Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取。

InputStream Reader
字节流,以byte为单位 字符流,以char为单位
读取字节(-1,0~255) 读取字符(-1,0~65535)
读到字节数组:int read(byte[] b) 读到字符数组int read(char[] c)

java.io.Reader是所有字符输入超类,它最主要的方法是

  1. public int read() throws IOException;

这个方法读取字符流的下一个字符,并返回字符表示的int,范围是0~65535。如果已读到末尾,返回-1

FileReader

FileReaderReader的一个子类,它可以打开文件并获取Reader。下面的代码演示了如何完整地读取一个FileReader的所有字符。

  1. public void readFile() {
  2. Reader reader = new FileReader("setting.properties");
  3. for(;;){
  4. int n = reader.read();
  5. if (n == -1) { break;}
  6. }
  7. System.out.println((char) n);
  8. reader.close();
  9. }

如果我们读取一个纯ASCII编码的文本文件,上述代码工作是没有问题的。但如果文件中包含中文,就会出现乱码,因为FileReader的默认编码与系统相关,例如,Windows系统的默认编码可能是GBK,打开 一个UTF-8编码的文本文件就会出现乱码。
要避免乱码问题,我们需要在创建FileReader时指定编码

java 11以上可用

  1. try(Reader reader = new FileReader("src/readme.txt",StandardCharsets.UTF_8) {
  2. ...
  3. }
  1. try(Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt"),StandardCharsets.UTF_8)) {
  2. }

同样,Reader也提供了一次性读取若干字符并填充到char[]数组的方法

  1. public int read(char[] c) throws IOException

它返回实际读入的字符个数,最大不超过char[]数组的长度。返回-1表示流结束。

利用这个方法,我们可以先设置一个缓冲区, 然后,每次尽可能地填充缓冲区

  1. public void readFile() throws IOException {
  2. try(Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt"),StandardCharsets.UTF_8))) {
  3. int n;
  4. char[] chars = new Char[1000];
  5. while( ( n = reader.read(chars) ) != -1) {
  6. System.out.println(Arrays.toString(chars));
  7. }
  8. }
  9. }

CharArrayReader

CharArrayReader可以在内存中模拟一个Reader,它的作用实际上是把一个char[]数组变成 一个Reader,这和ByteArrayInputStream非常类似。

  1. try(Reader reader = new CharArrayReader("Hello".toCharArray())) {
  2. }

StringReader

StringReader可以直接把String作为数据源,它和CharArrayReader几乎一样。

  1. try(Reader reader = new StringReader("Hello")) {
  2. }

InputStreamReader

ReaderInputStream有什么关系?

除了特殊的CharArrayReaderStringReader,普通的Reader实际上是基于InputStream构造的,因为Reader需要从InputStream中读入字节流(byte),然后根据编码设置,再转换为char就可以实现字符流。

如果我们查看FileReader的源码,它在内部实际上持有一个FileInputStream

既然Reader本质上是一个基于InputStreambytechar的转换器,那么,如果我们已经有一个InputStream,想把它转换为Reader,是完全可行的。InputStreamReader就是这样一个转换器,它可以把任何InputStream转换成Reader

  1. InputStream input = new FileInputStream("src/readme.txt");
  2. Reader reader = new InputStreamReader(input,StandardCharsets.UTF_8);

构造InputStreamReader时,我们需要传入InputStream,还需要指定编码,就可以得到一个Reader对象。
上述代码可以通过try(resource)

  1. try(Reader reader = new InputStreamReader(new FileInputStream("src/readme.txt"),StandardCharts.UTF_8)) {
  2. }

上述代码实际上就是FileReader的一种实现方式。
使用tr(resource)结构时,当我们关闭Reader时,它会在内部自动调用InputStreamclose()方法,所以,只需要关闭最外层的Reader对象即可。 :::info 使用inputStreamReader,可以把一个InputStream转成Reader。 :::

小结

Reader定义了所有字符输入流的超类

  • FileReader实现了文件字符流输入,使用时要指定编码
  • CharArrayReaderStringReader可以在内存中模拟一个字符流输入。

Reader是基于InputStream构造的,可以通过InputStreamReader在同时将任何InputStream转换为Reader
总是使用try(resource)保证Reader正确关闭。