Reader
是Java的IO库提供的另一个输入流接口。和InputStream
的区别是,InputStream
是一个字节流,即以byte
为单位读取,而Reader
是一个字符流,即以char
为单位读取:
InputStream | Reader |
---|---|
字节流,以byte 为单位 | 字符流,以char 为单位 |
读取字节(-1,0~255):int read() | 读取字符(-1,0~65535):int read() |
读到字节数组:int read(byte[] b) | 读到字符数组:int read(char[] c) |
java.io.Reader
是所有字符输入流的超类,它最主要的方法是:
public int read() throws IOException;
这个方法读取字符流的下一个字符,并返回字符表示的int
,范围是0
~65535
。如果已读到末尾,返回-1
。
FileReader
FileReader
是Reader
的一个子类,它可以打开文件并获取Reader
。下面的代码演示了如何完整地读取一个FileReader
的所有字符:
public void readFile() throws IOException { // 创建一个FileReader对象: Reader reader = new FileReader("src/readme.txt"); // 字符编码是??? for (;;) { int n = reader.read(); // 反复调用read()方法,直到返回-1 if (n == -1) { break; } System.out.println((char)n); // 打印char } reader.close(); // 关闭流}
如果我们读取一个纯ASCII编码的文本文件,上述代码工作是没有问题的。但如果文件中包含中文,就会出现乱码,因为FileReader
默认的编码与系统相关,例如,Windows系统的默认编码可能是GBK
,打开一个UTF-8
编码的文本文件就会出现乱码。
要避免乱码问题,我们需要在创建FileReader
时指定编码:
Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
和InputStream
类似,Reader
也是一种资源,需要保证出错的时候也能正确关闭,所以我们需要用try (resource)
来保证Reader
在无论有没有IO错误的时候都能够正确地关闭:
try (Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8) { // TODO}
Reader
还提供了一次性读取若干字符并填充到char[]
数组的方法:
public int read(char[] c) throws IOException
它返回实际读入的字符个数,最大不超过char[]
数组的长度。返回-1
表示流结束。
利用这个方法,我们可以先设置一个缓冲区,然后,每次尽可能地填充缓冲区:
public void readFile() throws IOException { try (Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8)) { char[] buffer = new char[1000]; int n; while ((n = reader.read(buffer)) != -1) { System.out.println("read " + n + " chars."); } } }
CharArrayReader
CharArrayReader
可以在内存中模拟一个Reader
,它的作用实际上是把一个char[]
数组变成一个Reader
,这和ByteArrayInputStream
非常类似:
try (Reader reader = new CharArrayReader("Hello".toCharArray())) { }
StringReader
StringReader
可以直接把String
作为数据源,它和CharArrayReader
几乎一样:
try (Reader reader = new StringReader("Hello")) { }
InputStreamReader
Reader
和InputStream
有什么关系?
除了特殊的CharArrayReader
和StringReader
,普通的Reader
实际上是基于InputStream
构造的,因为Reader
需要从InputStream
中读入字节流(byte
),然后,根据编码设置,再转换为char
就可以实现字符流。如果我们查看FileReader
的源码,它在内部实际上持有一个FileInputStream
。
既然Reader
本质上是一个基于InputStream
的byte
到char
的转换器,那么,如果我们已经有一个InputStream
,想把它转换为Reader
,是完全可行的。InputStreamReader
就是这样一个转换器,它可以把任何InputStream
转换为Reader
。示例代码如下:
// 持有InputStream:InputStream input = new FileInputStream("src/readme.txt");// 变换为Reader:Reader reader = new InputStreamReader(input, "UTF-8");
构造InputStreamReader
时,我们需要传入InputStream
,还需要指定编码,就可以得到一个Reader
对象。上述代码可以通过try (resource)
更简洁地改写如下: