欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java的IO流

程序员文章站 2022-07-03 14:22:21
在Java中,把不同的输入/输出源(键盘、文件、网络连接等)中的有序数据抽象为流(stream)。 stream(流)是从起源(source)到接收(sink)的有序数据。 通过流的方式,Java可以使用相同的方式来访问、操作不同类型的输入/输出源,不管输入、输出节点是磁盘文件、网络连接,还是其他的 ......

 

在java中,把不同的输入/输出源(键盘、文件、网络连接等)中的有序数据抽象为流(stream)。

stream(流)是从起源(source)到接收(sink)的有序数据。

通过流的方式,java可以使用相同的方式来访问、操作不同类型的输入/输出源,不管输入、输出节点是磁盘文件、网络连接,还是其他的io设备,只要将这些节点包装成流,我们就可以使用相同的方式来进行输入、输出操作。

原本进行文件读写要用文件读写的一套方法,进行网络读写要用网络读写的一套方法.....不同类型的io节点,用的方法体系不同,要单独写代码。

java统一了不同类型节点的io操作,都把输入、输出节点包装成流,使用一套方法就可以进行文件读写、网络读写等不同类型节点的读写,不必单独写代码,从而简化了io操作。

 

 

 

 

流的分类:

1、按流的方向分:

  • 输入流:从磁盘、网络等读取到内存,只能读,不能写。
  • 输出流:从内存写出到磁盘、网络,只能写,不能读。

 

2、按操作的数据单元分:

  • 字节流:以字节为基本操作单位
  • 字符流:以字符为基本操作单位

字节流、字符流的用法基本一样,只是操作的数据单元不同。

 

3、按照流的角色来分:

  • 节点流:向某个io设备/节点(磁盘文件、网络等)直接读写数据,也称为低级流。
  • 处理流:用于包装一个已存在的流,通过这个已存在的流来进行读写操作,并不直接操作io节点,也称为高级流、包装流。

处理流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,可以消除不同节点流的实现差异,访问不同的数据源。

 

 

 

 

 

inputstream是字节输入流的*父类,常用方法:

  • int  read()     //读取一个字节,返回该字节数据的unicode码值
  • int  read(byte[]  buff)    //最多读取buff.length个字节,将读取的数据放在buff数组中,返回实际读取的字节数
  • int  read(byte[]  buff, int off, int length)    //最多读取length个字节,放在buff数组中,从数组的off位置开始放置数据,返回实际读取的字节数。off一般设置为0,length一般设置为buff的长度(这样其实和第二个函数的功能完全一样)。

 

reader时字符输入流的*父类,常用方法:

  • int  read()     //读取一个字符,返回该字符的unicode码值,注意并不是返回该字符。
  • int  read(char[]  buff)   //最多读取buff.length个字符,放在buff数组中,返回实际读取的字符数
  • int  read(char[]  buff, int  off, int  length) //最多读取length个字节,放在buff数组中,从数组的off位置开始放置数据,返回实际读取的字符数。off一般设置为0,length一般设置为buff的长度(这样其实和第二个函数的功能完全一样)。

 

字节流和字符流的用法差不多,只是操作的数据单元不同。

如果已读完(没有数据可读),以上方法均返回 -1 。

inputstream、reader都是抽象类,不能直接创建示例,要用已实现的子类创建实例,比如fileinputstream类、filereader类。

 

示例:

 1 fileinputstream fis=new fileinputstream("./1.txt");
 2         /*
 3         因为是以字节为单位操作的,如果要将byte[]转换为string,中文、byte[]长度设置不对,就可能读取到中文字符的半个字节,最终得到的string会乱码。
 4         有2种解决中文乱码的方式:
 5         1、将数组长度设置为较大的值(大于等于输入流的内容长度)
 6         2、如果输入节点以gbk、gb2312编码(2字节码),将数组长度设置为2的倍数;如果输入节点以utf-8编码(3字节码),将数组长度设置为3的倍数
 7         注意:在windows的资源管理器中新建文本文件,默认字符集是ascii,保存时注意修改编码方式。
 8         */
 9        byte[] buff=new byte[1024];
10        int length=0;
11         while ((length=fis.read(buff))!=-1){
12             //使用string的构造函数将byte[]转化为string。此处用new string(buff)也是一样的
13             system.out.println(new string(buff,0,length));     
14         }
15         //使用完,要关闭流
16         fis.close();

 

1 filereader fr=new filereader("./1.txt");
2         //因为操作单位是一个字符,所以数组大小设置为多少都行,不会乱码。
3         char[] buff=new char[1024];
4         while (fr.read(buff)!=-1){
5             system.out.println(new string(buff));
6         }
7         fr.close();

 

 

inputstream、reader均提供了移动记录指针的方法:

  • long  skip(long  n)     //指针向前移动n个字节/字符
  • void  mark(int maxreadlimit)    //在当前指针处作一个标记。参数指定此标记的有效范围,mark()做标记后,再往后读取maxreadlimte字节,此标记就失效。

实际上,并不完全由maxreadlimte参数决定,也和bufferedinputstream类的缓冲区大小有关。只要缓冲区够大,mark()后读取的数据没有超出缓冲区的大小,mark标记就不会失效。如果不够大,mark后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。

  • void  reset()   将指针重置到上一个mark()标记的位置
  • boolean  marksupported()     //检查这个流是否支持mark()操作

以上方法只能用于输入流,不能用于输出流。

 

 

 

 

 

 

outputstream是字节输出流的*父类,常用方法:

  • void  write(int  i)    \\输出一个字节,i是码值,即read()得到的码值,输出i对应的字节
  • void  write(byte[]  buff)   //输出整个字节数组的内容
  • void  write(byte[]  buff, int  off, int  length)    //把字节数组从off位置开始,输出长度为length字节的内容

 

writer是字符输出流的*父类,常用方法:

  • void  write(int  i)    //输出一个字符,i是码值,输出的是i对应的字符
  • void  write(char[]  buff)    //输出整个char[]的内容
  • void  write(char[]  buff,  int  off,  int  length)      //把char[]从off位置开始,输出长度为length字符的内容

可以用string代替char[],所以writer还具有以下2个方法:

  • void  write(string str)
  • void  write(string str, int off, int length)

 

outputstream、writer是抽象类,不能实例化,要创建对象,需使用已实现的子类,比如fileoutputstream、filewriter。

 

示例:

1  //默认是覆盖
2         fileoutputstream fos=new fileoutputstream("./2.txt");
3         fos.write("你好!\n".getbytes());
4         fos.close();
5 
6         //可指定是否是追加模式,true——是追加模式,false——不是追加模式(即覆盖)
7         fos=new fileoutputstream("./2.txt",true);
8         fos.write("我是chenhongyong。".getbytes());
9         fos.close();

 

1 //默认是覆盖
2         filewriter fw=new filewriter("./2.txt");
3         fw.write("hello\n");
4         fw.close();
5 
6         //追加
7         fw=new filewriter("./2.txt",true);
8         fw.write("world!");
9         fw.close();

 

 1 //如果输出流的文件对象不存在,会报错
 2         fileinputstream fis=new fileinputstream("./1.txt");
 3         //如果输出流的文件不存在,会自动创建
 4         fileoutputstream fos=new fileoutputstream("./2.txt");
 5         byte[] buff=new byte[1024];
 6         //文件复制
 7         while (fis.read(buff)!=-1)
 8             fos.write(buff);
 9         fis.close();
10         fos.close();

 

windows的换行符是\r\n,linux、unix的换行符是\n。最好用\n,\n是所有os通用的换行符。

 

 

 

 

计算机中的文件可以分为二进制文件、文本文件,能用记事本打开、并可以看到字符内容的文件就是文本文件,反之称为二进制文件。

实际上,从底层来看,计算机上的所有文件都是基于二进制存储、读写的。计算机上的文件都是二进制文件,文本文件是一种特殊的二进制文件。

 

字节流的功能更强大,因为字节流可以操作所有的二进制文件(计算机上的所有文件)。

但如果用字节流处理文本文件,要进行字符、字节之间的转换,要麻烦一些。

所以一般情况下,我们用字符流处理文本文件,用字节流处理二进制文件。

 

 

 

 

上面使用的fileinputstream、filewriter、fileoutputstream、filewriter都是直接操作io节点(磁盘文件),构造函数里是io节点,它们都是节点流(低级流)。

处理流用来包装一个已存在的流,示例:

 1  fileoutputstream fos=new fileoutputstream("./1.txt");
 2         //以一个已存在的流对象作为参数
 3         printstream ps=new printstream(fos);
 4 
 5         //可以字节为单位进行io操作
 6         ps.write("你好!".getbytes());
 7         //可以字符为单位进行io操作
 8         ps.print("很高兴认识你!");
 9         //println()输出后直接换行,不用我们在写\n,更加简便
10         ps.println("我是chenhongyong。");
11         //可输出各种类型的数据
12         ps.write(12);
13         ps.print(12);
14         ps.print(12.34);
15 
16         //关闭处理流时,会自动关闭其包装的节点流,代码更简洁。
17         ps.close();
18 
19         //处理流提供了更多的输入/输出方法,更加简单、强大

printstream类的输出功能很强大,可以把输出的低级流包装成printstream使用。

 

处理流的优点:

  • 更简单、更强大
  • 执行效率更高

 

 

 

 

处理流之缓冲流:

前面提到可以用byte[]、char[]做缓冲区,提高读写效率。java提供了缓冲流,来实现相同的效果,使用方式基本相同。

 1  //以相应的节点流对象作为参数
 2         bufferedinputstream bis = new bufferedinputstream(new fileinputstream("./1.txt"));
 3         bufferedoutputstream bos = new bufferedoutputstream(new fileoutputstream("./2.txt"));
 4         //文件复制
 5         int i = 0;
 6         while ((i = bis.read()) != -1){   //length是读取到的内容的码值,不是读取到的内容的长度。一次只读一个字节。
 7             system.out.println(length);
 8             bos.write(length);   //将读取的数据写到输出流
 9     }
10         //关闭处理流即可,会自动关闭对应的节点流
11         bis.close();
12         bos.close();

 

 1  bufferedinputstream bis=new bufferedinputstream(new fileinputstream("./1.txt"));
 2        bufferedoutputstream bos=new bufferedoutputstream(new fileoutputstream("./2.txt"));
 3        byte[] buff=new byte[1024];
 4        int length=0;
 5        while ((length=bis.read(buff))!=-1){   //length是读取到的内容的长度
 6            system.out.println(length);
 7             bos.write(buff);
 8        }
 9        bis.close();
10        bos.close();

 

缓冲区:

计算机访问外部设备或文件,要比直接访问内存慢的多。如果我们每次调用read()方法或者writer()方法访问外部的设备或文件,cpu就要花上最多的时间是在等外部设备响应,而不是数据处理。
为此,我们开辟一个内存缓冲区的内存区域,程序每次调用read()方法或writer()方法都是读写在这个缓冲区中。当这个缓冲区被装满后,系统才将这个缓冲区的内容一次集中写到外部设备或读取进来给cpu。使用缓冲区可以有效的提高cpu的使用率,能提高读写效率。

缓冲流:

读写数据时,让数据在缓缓冲区能减少系统实际对原始数据来源的存取次数,因为一次能做多个数据单位的操作,相较而言,对于从文件读取数据或将数据写入文件,比起缓冲区的读写要慢多了。所以使用缓冲区的 流,一般都会比没有缓冲区的流效率更高,拥有缓冲区的流别称为缓冲流。缓冲流本身没有输入、输出功能,它只是在普通io流上加一个缓冲区。

 

缓冲流是和4级*父类对应的:加前缀buffered

inputstream   bufferedinputstream   字节输入缓冲流,可作为所有字节输入流类的缓冲流

outputstream   bufferedoutputstream    字节输出缓冲流

reader     bufferedreader    字符输入缓冲流

writer  bufferedwriter    字符输出缓冲流

 

 执行效率:

普通io流,逐个字节/字符操作,效率极低。缓冲流自带缓冲区,就算是逐个字节/字符操作,效率也是很高的。

普通io流,用数组作为缓冲区,效率极高。缓冲流使用数组,效率极高。二者实现方式是相同的。

大致上:使用数组的缓冲流 ≈ 使用数组的普通流 >>  逐个字节/字符操作的缓冲流 >>  逐个字节/字符操作的普通io流。

尽量不要逐个字节/字符操作,太慢了。

 

由于使用了缓冲区,写的数据不会立刻写到物理节点,而是会写到缓冲区,缓冲区满了才会把缓冲区的数据一次性写到物理节点。

可以使用流对象的flush()方法,强制刷出缓冲区的数据到物理节点。

当然,调用close()关闭流对象时,会先自动调用flush()刷出缓冲区的数据到物理节点。

 

 

 

 

 

处理流之转换流:java提供了2个转化流,用于将字节流转换为字符流。

1、inputstreamreader类,顾名思义,是从inputstream到reader。inputstreamreader是reader的子类,操作的数据单元是字符。

inputstreamreader  isr=new  inputstreamreader(inputstream is);     //参数是inputstream类的对象

 

2、outputstreamwriter类,顾名思义,是从outputstream到writer。outputstreamwriter是writer的子类,操作的数据单元是字符。

outputstreamwriter  isr=new  outputstreamwriter(outputstream os);     //参数是outputstream类的对象

 

因为字符流本就比较方便,所以没有将字符流转换为字节流的类。

 

 

 

 

 

 

预定义的标准流对象:

  • system.in     标准输入流对象(一般指键盘输入),是inputstream的一个对象,可使用inputstream的一切方法。
  • system.out    标准输出流对象(一般指显示器),是outputstream的一个对象
  • system.err    标准错误输出流。
1  byte[] buff=new byte[100];
2         //将键盘输入(从控制台输入)读取到byte[]中
3         system.in.read(buff);
4         //转换为string输出
5         system.out.println(new string(buff));

 

 

 重定向标准流对象:

  • system.setin(inputstream  is);     //将标准输入流切换到指定的inputstream对象
  • system.setout(printstream   ps);   
  • system.seterr(printstream  ps);
1 printstream ps=new printstream("./err.txt");
2         //重定向标准错误输出流,错误信息不再输出到控制台,而是输出到err.txt文件
3         system.seterr(ps);
4         int a=4/0;

 

1  printstream ps=new printstream("./out.txt");
2         //重定向标准输出流,标准输出不再输出到屏幕(控制台),而是输出到out.txt文件
3         system.setout(ps);
4         //1会输出到out.txt文件
5         system.out.println(1);

 

 

 

 

 

 

java常用的流:

 

分类  字节输入流  字节输出流 字符输入流 字符输出流
抽象基类   inputstream   outputstream reader writer
操作文件 fileinputstream    fileoutputstream   filereader   filewriter  
操作数组   bytearrayinputstream   bytearrayoutputstream   chararrayreader   chararraywriter  
操作字符串       stringreader stringwriter
缓冲流       bufferedinputstream bufferedoutputstream bufferedreader bufferedwriter
转换流     inputstreamreader     outputstreamwriter
对象流(用于序列化) objectinputstream objectoutputstream    
抽象基类(用于过滤)     filterinputstream filteroutputstream filterreader filterwriter
打印流(输出功能极其大)   printstream(实际上也可用于输出字符)   printwriter(不能输出byte)  

 

部分流可以在构造函数中以参数string charset或charset  charset的形式指定字符集。

 

此外,还有其他的io流,比如:

  • audioinputstream、audiooutputstream     音频输入、输出
  • cipherinputstream、cipheroutputstream      加密、解密
  • deflaterinputstream、zipinputstream、zipoutputstream    文件压缩、解压

后一篇随笔再介绍。