Reader
# Reader
Reader 是 Java 的 IO 库提供的另一个输入流接口。和 InputStream 的区别是,InputStream 是一个字节流,即以 byte 为单位读取,而 Reader 是一个字符流,即以 char 为单位读取
# Read 方法
java.io.Reader 是所有字符输入流的超类,它最主要的方法是 read():
public int read() throws IOException;
这个方法读取字符流的下一个字符,并返回字符表示的 int,范围是 0~65535。如果已读到末尾,返回 -1。
# FileReader
FileReader 是 Reader 的一个子类,它可以打开文件并获取 Reader。我们来实践下,读取一个 txt 文件并打印。
我们先新建一个 txt 文件,里面写一些英文(注意先不用中文,可能有编码问题)
然后我们编写代码读取和打印:
import java.io.FileReader
public class IODemo11Reader {
public static void main(String[] args) throws Exception{
FileReader reader = new FileReader("readme.txt");
int n = reader.read();
while(-1 != n){
System.out.println((char)n);
n = reader.read();
}
reader.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
运行结果:
$ javac IODemo12Writer.java -encoding utf8
$ java IODemo12Writer -encoding utf8
Hello
2
3
# 读入多个字符
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.");
}
}
}
2
3
4
5
6
7
8
9
# CharArrayReader
CharArrayReader 可以在内存中模拟一个 Reader,它的作用实际上是把一个 char[] 数组变成一个 Reader,这和 ByteArrayInputStream 非常类似:
Reader reader = new CharArrayReader("Hello".toCharArray()))
# StringReader
StringReader 可以直接把 String 作为数据源,它和 CharArrayReader 几乎一样:
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");
2
3
4
构造 InputStreamReader 时,我们需要传入 InputStream,还需要指定编码,就可以得到一个 Reader 对象。
上述代码实际上就是 FileReader 的一种实现方式。
使用 try (resource) 结构时,当我们关闭 Reader 时,它会在内部自动调用 InputStream 的 close() 方法,所以,只需要关闭最外层的 Reader 对象即可。
# 编码问题
FileReader 默认的编码与系统相关,例如,Windows 系统的默认编码可能是 GBK,打开一个 UTF-8 编码的文本文件就会出现乱码。要避免乱码问题,我们需要在创建的时候指定编码:
FileInputStream fis = new FileInputStream("readme.txt");
InputStreamReader reader = new InputStreamReader(fis,"UTF-8");
2
从 JDK11 开始,FileReader 新增加了几个构造方法,可以在创建的时候指定编码:
public FileReader(String fileName, Charset charset) throws IOException
# 小结
Reader 定义了所有字符输出流的超类:
FileReader实现了文件字符流输出;CharReader和StringReader在内存中模拟一个字符流输出。
Reader 是基于 InputStream 构造的,可以通过 InputStreamReader 将 InputStream 转换为 Reader,转换时需要指定编码。