在Java中,把不同的输入/输出源(键盘、文件、网络连接等)中的有序数据抽象为流(stream)。
stream(流)是从起源(source)到接收(sink)的有序数据。
通过流的方式,Java可以使用相同的方式来访问、操作不同类型的输入/输出源,不管输入、输出节点是磁盘文件、网络连接,还是其他的IO设备,只要将这些节点包装成流,我们就可以使用相同的方式来进行输入、输出操作。
原本进行文件读写要用文件读写的一套方法,进行网络读写要用网络读写的一套方法.....不同类型的IO节点,用的方法体系不同,要单独写代码。
java统一了不同类型节点的IO操作,都把输入、输出节点包装成流,使用一套方法就可以进行文件读写、网络读写等不同类型节点的读写,不必单独写代码,从而简化了IO操作。
流的分类:
1、按流的方向分:
- 输入流:从磁盘、网络等读取到内存,只能读,不能写。
- 输出流:从内存写出到磁盘、网络,只能写,不能读。
2、按操作的数据单元分:
- 字节流:以字节为基本操作单位
- 字符流:以字符为基本操作单位
字节流、字符流的用法基本一样,只是操作的数据单元不同。
3、按照流的角色来分:
- 节点流:向某个IO设备/节点(磁盘文件、网络等)直接读写数据,也称为低级流。
- 处理流:用于包装一个已存在的流,通过这个已存在的流来进行读写操作,并不直接操作IO节点,也称为高级流、包装流。
处理流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,可以消除不同节点流的实现差异,访问不同的数据源。
InputStream是字节输入流的顶级父类,常用方法:
Reader时字符输入流的顶级父类,常用方法:
字节流和字符流的用法差不多,只是操作的数据单元不同。
如果已读完(没有数据可读),以上方法均返回 -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均提供了移动记录指针的方法:
实际上,并不完全由maxReadLimte参数决定,也和BufferedInputStream类的缓冲区大小有关。只要缓冲区够大,mark()后读取的数据没有超出缓冲区的大小,mark标记就不会失效。如果不够大,mark后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。
以上方法只能用于输入流,不能用于输出流。
OutputStream是字节输出流的顶级父类,常用方法:
Writer是字符输出流的顶级父类,常用方法:
可以用String代替char[],所以Writer还具有以下2个方法:
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
普通IO流,逐个字节/字符操作,效率极低。缓冲流自带缓冲区,就算是逐个字节/字符操作,效率也是很高的。
普通IO流,用数组作为缓冲区,效率极高。缓冲流使用数组,效率极高。二者实现方式是相同的。
尽量不要逐个字节/字符操作,太慢了。
由于使用了缓冲区,写的数据不会立刻写到物理节点,而是会写到缓冲区,缓冲区满了才会把缓冲区的数据一次性写到物理节点。
可以使用流对象的flush()方法,强制刷出缓冲区的数据到物理节点。
当然,调用close()关闭流对象时,会先自动调用flush()刷出缓冲区的数据到物理节点。
处理流之转换流:Java提供了2个转化流,用于将字节流转换为字符流。
1、InputStreamReader类,顾名思义,是从InputStream到Reader。InputStreamReader是Reader的子类,操作的数据单元是字符。
2、OutputStreamWriter类,顾名思义,是从OutputStream到Writer。OutputStreamWriter是Writer的子类,操作的数据单元是字符。
因为字符流本就比较方便,所以没有将字符流转换为字节流的类。
预定义的标准流对象:
1 byte[] buff=new byte[100]; 2 //将键盘输入(从控制台输入)读取到byte[]中 3 System.in.read(buff); 4 //转换为String输出 5 System.out.println(new String(buff));
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 |
操作文件 | FileOutputStream | FileReader | FileWriter | |
操作数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
操作字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流(用于序列化) | ObjectInputStream | ObjectOutputStream | ||
FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
打印流(输出功能极其大) | PrintStream(实际上也可用于输出字符) | PrintWriter(不能输出byte) |
此外,还有其他的IO流,比如:
后一篇随笔再介绍。