java io 学习笔记(三) 字符流读写
1、字符流读取
字符流读取的所有类都是从Reader这个超类继承的,都是用于读取字符的,这些类分别是InputSteamReader(从字符流读取)、FileReader(继承与InputStreamReader,读取文件流)StringReader(读取字符串)、PipedReader(读取管道,管道的上端来自于一个PipedWriter)、CharArrayReader(读取字符数组),还有两个比较特殊的类,一个是FileterReader,这是个抽象类,目前只有PushbackReader类实现了它,从名字就可以看出PushbackReader类允许把读出来的数据推回流中(但能不能推到源中,还有待于验证);一个是BufferedReader,这个是一个为了提高字符流读取的性能而提供的带有缓存功能的类,是一个代理类,能代理前面提到的所有类,也就是说,它可以读取所有来源的数据。
InputStreamReader和FileReader
这个类的字符流是从字节流读取字节数据,并且把字节数据转化为字符的,也就是说字符流的读操作其实是基于字节流读操作之上的,内部调取了字节流的读操作。所以很重要的两个传入参数就是字节流和字符编码集。因为文件操作很重用,所有从InputStreamReader里面有专门派生出了一个FileReader,FileReader因为读取的是文件,字符流可以用文件或者文件名代替,这样源就是且只能是一个文件了。
这个类与其他读字符流读取类最大的区别,就是它没有mark、skip、reset等和读取位置有关的方法,这是因为他是从流中读取字节数据的,因此没有办法对流进行标记,只能老老实实重头到尾按顺序取字节流。所以他只有close,read,ready,getEncoding四个函数
PushbackReader
这个类主要是提供了unread()操作,可以把字符写回。
PipedReader
这个类从和它相连(snc.connect(src)的PipedWriter中读取字节流。
BufferedReader
这个类主要是提供一个其它类的代理功能,并且提供一个缓存,尤其是用在InputStream时,更能提高效率,因为他可以提前把字节流读取到缓存中,所以其他类一般都不会直接用,而是用BufferedReader包装一下再用。特别需要注意的是,这个类没有提供直接读取字节流的构造函数,也就是说它必须从字符流中读取数据。与它对应的Buffer额度Writer也是这样,只能把字符写到字符流中,而不能直接写到字节流中去。也就是说,它没有提供字符到字节的转码功能。
注意:为了优化性能,所有的数据字符流读取类从字节流中读取数据时不是一个一个字符的数据量读的,而是一下读取很多数据,然后把这些数据放在缓存里面,再根据需求从缓存里面把字符读出来。所以,当有两个字符流读取对象对同一个block字节流进行读取时,虽然前一个字符流只读取了一个字符出来,但是因为字节的预读取,所以后一个对象可能就无法从字节流中读取数据了,如果此时后面这个对象的缓存里面也没有数据,它就会一直卡在那里,等待字节流的数据。
1 public class InputStreamIO {
2 public static void main(String args[]) {
3 try {
4 int count = 0;
5 InputStreamReader inputStreamReader = new InputStreamReader(System.in);
6 PushbackReader pushbackReader = new PushbackReader(new InputStreamReader(System.in));
7 System.out.println("the ecoding of inputStreamReader is :" + inputStreamReader.getEncoding());
8 char mychar;
9 do {
10 mychar = (char)inputStreamReader.read();
11 // pushbackReader.unread(mychar);
12 System.out.println("char from inputStreadReader:"+mychar);
13 System.out.println("char from pushbackReader:"+(char)pushbackReader.read());
14 } while (mychar!='q');
15 }
16 catch (IOException e){
17 System.out.println(e.getMessage());
18 e.printStackTrace();
19 }
20 }
21 }
上面的代码是从标准输入流中读取数据的,结果如下:
the ecoding of inputStreamReader is :GBK 123456 char from inputStreadReader:1 12 char from pushbackReader:1 char from inputStreadReader:2 char from pushbackReader:2 char from inputStreadReader:3 char from pushbackReader: char from inputStreadReader:4
上面的结果中,当输入123456之后,inputStreadReader读取了一个字符到mychar中,虽然只读取了一个字符,但是它把标准输入流中的所有数据都读完了,存在它的缓存里面,后面的pushbackReader因为自己的缓存里面没有数据,所以它试图从标准输入中获取字节,但此时标准输入中所有的字节都被前面的inputStreadReader读取完了,标准输入流里已经没有数据了,所以后面的pushbackReader就只能一直等在那里。
第二次输入12之后,标准输入流里面就有了“12\n“(注意,因为标准输入流以换行符提交刷新,即flush,所以最后肯定会有一个换行符),这是pushbackReader便有了字节可以读取,它一下子把“12\n”读取完了,存在缓存里,并且从缓存里读取出1,被打印了出来
之后循环继续,到InputStreamReader时,它的缓存里面还有"23456\n",所以它不需硬要从标准输入流中读如字节数据,所以它不会卡在那里,他顺利的读取了2;同样,后面的pushbackReader它也还有“2\n”,它把2读了出来了。这样顺利的循环下去,知道pushbackReader读取我它缓存里的最后一个数据“\n”之后,它又卡在了那里。
2、字符流写
字符流写就是把字符写到相应的目的地,并且和字符流读是对应的,基本上就是把原来的xxxReader变成xxxWriter,然后功能就由读变成了写,源也就变成了对应的目的地。如果目的地是字符流(这种情况只有OutputStreamWriter以及它的子类FileWriter),则会把字符自动编码成字节后再写入流当中。
PrintWriter这个类比较特殊,下面重点说说它。
PrintWriter
PrintWriter其实是OutputStreamWriter和FileWriter的增强版,它包括了格式化输出,按行输出以及自动添加换行符等功能(println)。另外有一点需要注意,它添加的换行符是和底层操作系统有关的,在Windows下,它添加的是回车换行符”\r\n“,在GNU下,他添加的是换行符“\n“。PrintWriter还能自动刷新缓存,每当调用了println, printf, 或者format中的任何一个函数之后,就自动flush。
我们还将马上看到,在字节流输出中,也有一个和PrintWriter比较像的叫PrintStream的对象,它也实现了把字符输出到字节流并提供编码功能的方法(所以说它比较特殊),但是它的换行符总是“\n”,且遇到“\n”就调用flush。
3、重点分析分析PipedReader和PipedWriter
PipedReader和PipedWriter是连起来用的,用于线程间的IO通信。PipedWriter是PipedReader的src(源),PipedReader是PipedWriter的snk(目标),他们通过二者任何一个connect函数连接,也可以在通过构造函数参数建立连接,并且src和snk是一对一的关系,否则会抛出IOException("Already connected")
异常。
其实src和snk通信挺简单的,就是src的writer
向snk的buffer里面写数据,具体的方式这是调用snk的receive
函数把字符传给snk的buffer。下面重点看看PipedReader的receive
函数的源码:
1 /** 2 * Receives a char of data. This method will block if no input is 3 * available. 4 */ 5 synchronized void receive(int c) throws IOException { 6 if (!connected) { 7 throw new IOException("Pipe not connected"); 8 } else if (closedByWriter || closedByReader) { 9 throw new IOException("Pipe closed"); 10 } else if (readSide != null && !readSide.isAlive()) { 11 throw new IOException("Read end dead"); 12 } 13 14 writeSide = Thread.currentThread(); 15 while (in == out) { 16 if ((readSide != null) && !readSide.isAlive()) { 17 throw new IOException("Pipe broken"); 18 } 19 /* full: kick any waiting readers */ 20 notifyAll(); 21 try { 22 wait(1000); 23 } catch (InterruptedException ex) { 24 throw new java.io.InterruptedIOException(); 25 } 26 }
/* 说明此PipedReader空了,此时把in和out都归位到0*/ 27 if (in < 0) { 28 in = 0; 29 out = 0; 30 } 31 buffer[in++] = (char) c; 32 if (in >= buffer.length) { 33 in = 0; 34 } 35 }
还有PipedReader的的read
函数的源码:
1 public synchronized int read() throws IOException { 2 if (!connected) { 3 throw new IOException("Pipe not connected"); 4 } else if (closedByReader) { 5 throw new IOException("Pipe closed"); 6 } else if (writeSide != null && !writeSide.isAlive() 7 && !closedByWriter && (in < 0)) { 8 throw new IOException("Write end dead"); 9 } 10 11 readSide = Thread.currentThread(); 12 int trials = 2; 13 while (in < 0) { 14 if (closedByWriter) { 15 /* closed by writer, return EOF */ 16 return -1; 17 } 18 if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) { 19 throw new IOException("Pipe broken"); 20 } 21 /* might be a writer waiting */ 22 notifyAll(); 23 try { 24 wait(1000); 25 } catch (InterruptedException ex) { 26 throw new java.io.InterruptedIOException(); 27 } 28 } 29 int ret = buffer[out++]; 30 if (out >= buffer.length) { 31 out = 0; 32 } 33 if (in == out) { 34 /* now empty */ 35 in = -1; 36 } 37 return ret; 38 }
可以看到,PipedReader是通过in和out两个指针来确定写入和读出的,如果有数据且不满,那么in!=out;当in==out且不等于-1时,表示buffer满了;当in=-1时,表示buffer空了。buffer是一个首尾相接的数组,从out到in是缓存的数据。从receive函数的源码中可以很容易地看出,当buffer满了(in==out且in>0),receive函数会唤醒其它线程,并等待1000ms让其它线程(读线程)有机会把数据读走。并且注意到,receive函数是同步的,也就是说只能有有个线程在写数据,并且可以肯定的是,这个线程是个写数据线程(snk的receive函数一般只由src的write函数调用,因为receive函数的访问权限是默认访问权限,只有同一个包小面的类才能访问它)。
另外,从PipedReader的read函数也可以看出,当buffer空了的时候(in<0),他也会唤醒其它线程,并且等待1000ms以期待其它线程写入数据到buffer。