Java BIO NIO

BIO基础

BIO全称是Blocking I/O,意为非阻塞IO。我们平时使用的IO API就是BIO。

BIO是面向流的。输入流读取数据,输出流写入数据。流基类是InputStreamOutputStream。但我们不能直接使用基类,只能使用其子类。基类声明了子类最基本也是最常用的 read/write 方法。

InputStreamOutputStream是直接操作字节数组的。使用它们需要手动把字符转为字节输入,字节转为字符读出。它们有“一次读/写一个字节”和“一次读/写多个字节”的方法,如下。

1
2
3
4
5
6
7
8
int read()
int read(byte[] data)
int read(byte[] data, int offset, int length)


void write(int b)
void write(byte[] data)
void write(byte[] data, int offset, int length)

“一次读/写一个字节”的方法因为效率低下所以很少用。“一次读/写多个字节”的方法很常用。read 方法数据流向:input -> byte[]。write 方法数据流向:byte[] -> output。read 方法返回值int代表实际读取的字节数。

常用子类:

  1. FileInputStream、FileOutputStream
  2. BufferedInputStream、BufferedOutputStream
  3. ByteArrayInputStream、ByteArrayOutputStream

BufferedInputStream和BufferedOutputStream

BufferedOutputStream将写入的数据存储在缓冲区中,缓冲区是一个名为buf的字节数组。等到缓冲区满或刷新输出流时,它将数据一次性全部写入底层输出流。因此,调用 BufferedOutputStream 的 write 方法之后,需要手动调用flush()方法把缓冲区数据写入底层输出流。

BufferedInputStream也有一个名为buf的字节数组作缓冲区。调用 read 方法时,它首先从缓冲区读取数据,只有当缓冲区没有数据时,它才会从底层输入流读取数据。和其他输入流子类不同的是,当BufferedInputStream的 read 方法需要从底层读取数据时,它会一直读到“输入流阻塞”或“没有数据可读”时,这些数据会放到缓冲区,然后把缓冲区数据的全部或部分填充到 byte[] 数组。其他输入流在返回前,只从底层读取一次,不会尝试多次读取直到无可读。

构造函数:

1
2
3
4
5
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int bufferSize)

BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int bufferSize)

第一个参数就是底层输入/输出流,第二个参数是指定缓冲区大小(单位为字节)。默认情况下,输入/输出流的 bufferSize 为8192字节。

BufferedInputStream和BufferedOutputStream的 read/write 方法同基类,无新增方法。

ByteArrayInputStream和ByteArrayOutputStream

构造函数:

1
2
3
4
5
ByteArrayInputStream(byte buf[])
ByteArrayInputStream(byte buf[], int offset, int length)

ByteArrayOutputStream()
ByteArrayOutputStream(int size)

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
2
3
4
5
6
7
8
9
10
11
12
InputStream input =  httpconn.getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
baos.write(buffer, 0, len);
}
baos.flush();
// 复制
InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());
InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());

处理字符的Reader和Writer

ReaderWriter直接操作的是字符而不是字节。字节和字符互相转换需要指定编码方式charset。Reader 和 Writer 默认使用的编码方式来自于Charset.defaultCharset()方法返回值。

Charset.defaultCharset() 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}

在上面方法中,首先取file.encoding属性值,找不到该属性值则返回”UTF-8”。file.encoding属性值在 Eclipse 中设置在 Project -> Properties -> Resource -> Text file encoding。项目不是在 Eclipse 中运行时,file.encoding 就是系统编码。

Reader 和 Writer 是基类,和 XXXStream 类一样,规定了子类的基本方法,包括“一次读/写一个字符”和“一次读/写多个字符”,如下:

1
2
3
4
5
6
7
8
9
10
int read()
int read(char[] text)
int read(char[] text, int offset, int length)


void write(int c)
void write(char[] text)
void write(char[] text, int offset, int length)
void write(String s)
void write(String s, int offset, int length)

和 XXXStream 类方法很相似,不同的是字节数组变成了字符数组(字符串),且 read 方法返回值 int 表示实际读取的字符数

重要子类:

  1. InputStreamReader、OutputStreamWriter
  2. BufferedReader、BufferedWriter
  3. StringReader、StringWriter

InputStreamReader和OutputStreamWriter

这两个类是 Reader 和 Writer 最基本的子类,也是 XXXStream 的包装类。InputStreamReader从其底层输入流中读取字节,再根据 charset 把字节转为字符返回。OutputStreamWriter接收字符数组,根据 charset 将字符转为字节,写入底层输出流。

构造函数:

1
2
3
4
5
InputStreamReader(InputStream in)
InputStreamReader(InputStream in, String charsetName)

OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out, String charsetName)

BufferedReader和BufferedWriter

与 BufferedInputStream 和 BufferedOutputStream 相似,BufferedReaderBufferedWriter使用内部的字符数组作为缓冲区,读取时先从缓冲区读取,写入时先写入缓冲区,只有当缓冲区满或 flush 时,缓冲区内容才写入底层输出流。

构造函数:

1
2
3
4
5
BufferedReader(Reader in)
BufferedReader(Reader in, int bufferSize)

BufferedWriter(Writer out)
BufferedWriter(Writer out, int bufferSize)

bufferSize为缓冲区字符数组大小,默认为8192字符。

StringReader和StringWriter

构造函数:

1
2
3
4
StringReader(String s)

StringWriter()
StringWriter(int initialSize)

StringReaderStringWriter都没有与底层输入/输出流交互。StringReader 的成员变量就是一个字符串String s,StringWriter 的成员变量是StringBuffer buf,这也是它们存放数据的地方。读取的数据来源是 String s,写入数据的目的地是 StringBuffer buf。StringWriter 构造函数中 initialSize 是设置 StringBuffer buf 的大小。

这两个类提供了字符串与 Reader、Writer 之间互相转换的便利。StringWriter 的toString()方法就可以直接输出 StringBuffer buf 的内容,非常方便。

NIO