Java的IO流
在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 文件压缩、解压
后一篇随笔再介绍。
上一篇: 很强大!全球首款11合1汽车移动电源现身
下一篇: cad迷你看图王打开总是闪烁怎么办?