BIO基础
BIO全称是Blocking I/O,意为非阻塞IO。我们平时使用的IO API就是BIO。
BIO是面向流的。输入流读取数据,输出流写入数据。流基类是InputStream和OutputStream。但我们不能直接使用基类,只能使用其子类。基类声明了子类最基本也是最常用的 read/write 方法。
InputStream和OutputStream是直接操作字节数组的。使用它们需要手动把字符转为字节输入,字节转为字符读出。它们有“一次读/写一个字节”和“一次读/写多个字节”的方法,如下。
1  | int read()  | 
“一次读/写一个字节”的方法因为效率低下所以很少用。“一次读/写多个字节”的方法很常用。read 方法数据流向:input -> byte[]。write 方法数据流向:byte[] -> output。read 方法返回值int代表实际读取的字节数。
常用子类:
- FileInputStream、FileOutputStream
 - BufferedInputStream、BufferedOutputStream
 - ByteArrayInputStream、ByteArrayOutputStream
 
BufferedInputStream和BufferedOutputStream
BufferedOutputStream将写入的数据存储在缓冲区中,缓冲区是一个名为buf的字节数组。等到缓冲区满或刷新输出流时,它将数据一次性全部写入底层输出流。因此,调用 BufferedOutputStream 的 write 方法之后,需要手动调用flush()方法把缓冲区数据写入底层输出流。
BufferedInputStream也有一个名为buf的字节数组作缓冲区。调用 read 方法时,它首先从缓冲区读取数据,只有当缓冲区没有数据时,它才会从底层输入流读取数据。和其他输入流子类不同的是,当BufferedInputStream的 read 方法需要从底层读取数据时,它会一直读到“输入流阻塞”或“没有数据可读”时,这些数据会放到缓冲区,然后把缓冲区数据的全部或部分填充到 byte[] 数组。其他输入流在返回前,只从底层读取一次,不会尝试多次读取直到无可读。
构造函数:
1  | BufferedInputStream(InputStream in)  | 
第一个参数就是底层输入/输出流,第二个参数是指定缓冲区大小(单位为字节)。默认情况下,输入/输出流的 bufferSize 为8192字节。
BufferedInputStream和BufferedOutputStream的 read/write 方法同基类,无新增方法。
ByteArrayInputStream和ByteArrayOutputStream
构造函数:
1  | ByteArrayInputStream(byte buf[])  | 
ByteArrayInputStream的参数 byte buf[] 是这个输入流数据的来源。当调用 ByteArrayInputStream 的 read(byte[] data) 方法时,就是把这个 byte buf[] 的数据复制到 byte[] data 中。offset 和 length 参数和上面所说的 read/write 方法的含义一致。
ByteArrayOutputStream内部也有一个字节数组buf,参数size就是指定这个字节数组的大小(默认值32)。当调用 ByteArrayOutputStream 的 write(byte[] data) 方法时,就是把这个 byte[] data 的数据复制到 byte buf[] 中。
ByteArrayXXX 和 BufferedXXX 的区别在于,ByteArrayXXX 没有用底层输入/输出流,它只把数据存在字节数组 buf 中。而 BufferedXXX 和底层流有交互,字节数组 buf 只是它和底层流之间的缓冲。在使用场景上,BufferedXXX 常用在网络IO,因为读/写缓冲区的速度比直接读/写网络流要快。ByteArrayXXX 常用于复制输入流的内容,示例如下:
1  | InputStream input = httpconn.getInputStream();  | 
处理字符的Reader和Writer
Reader和Writer直接操作的是字符而不是字节。字节和字符互相转换需要指定编码方式charset。Reader 和 Writer 默认使用的编码方式来自于Charset.defaultCharset()方法返回值。
Charset.defaultCharset() 源码:
1  | public static Charset defaultCharset() {  | 
在上面方法中,首先取file.encoding属性值,找不到该属性值则返回”UTF-8”。file.encoding属性值在 Eclipse 中设置在 Project -> Properties -> Resource -> Text file encoding。项目不是在 Eclipse 中运行时,file.encoding 就是系统编码。
Reader 和 Writer 是基类,和 XXXStream 类一样,规定了子类的基本方法,包括“一次读/写一个字符”和“一次读/写多个字符”,如下:
1  | int read()  | 
和 XXXStream 类方法很相似,不同的是字节数组变成了字符数组(字符串),且 read 方法返回值 int 表示实际读取的字符数。
重要子类:
- InputStreamReader、OutputStreamWriter
 - BufferedReader、BufferedWriter
 - StringReader、StringWriter
 
InputStreamReader和OutputStreamWriter
这两个类是 Reader 和 Writer 最基本的子类,也是 XXXStream 的包装类。InputStreamReader从其底层输入流中读取字节,再根据 charset 把字节转为字符返回。OutputStreamWriter接收字符数组,根据 charset 将字符转为字节,写入底层输出流。
构造函数:
1  | InputStreamReader(InputStream in)  | 
BufferedReader和BufferedWriter
与 BufferedInputStream 和 BufferedOutputStream 相似,BufferedReader和BufferedWriter使用内部的字符数组作为缓冲区,读取时先从缓冲区读取,写入时先写入缓冲区,只有当缓冲区满或 flush 时,缓冲区内容才写入底层输出流。
构造函数:
1  | BufferedReader(Reader in)  | 
bufferSize为缓冲区字符数组大小,默认为8192字符。
StringReader和StringWriter
构造函数:
1  | StringReader(String s)  | 
StringReader和StringWriter都没有与底层输入/输出流交互。StringReader 的成员变量就是一个字符串String s,StringWriter 的成员变量是StringBuffer buf,这也是它们存放数据的地方。读取的数据来源是 String s,写入数据的目的地是 StringBuffer buf。StringWriter 构造函数中 initialSize 是设置 StringBuffer buf 的大小。
这两个类提供了字符串与 Reader、Writer 之间互相转换的便利。StringWriter 的toString()方法就可以直接输出 StringBuffer buf 的内容,非常方便。