1. 引言
在 Java 的 I/O 体系中,Reader是一个非常重要的抽象类,它专门用于读取字符流。与InputStream(字节流)不同,Reader以字符为单位进行读取,天然支持 Unicode 字符集,因此在处理文本文件、网络报文等场景时更加方便和安全。
本文将从Reader的继承体系讲起,逐步深入到常用实现类、典型用法、性能对比以及常见坑点,帮助你全面掌握 Java 字符输入流的使用。
2. Reader 是什么
Reader是java.io包下的一个抽象类,是所有字符输入流的基类。它的核心职责是:从数据源(文件、字符串、字节数组等)读取字符数据。
2.1 核心方法
| 方法 | 说明 |
|---|---|
int read() | 读取单个字符,返回 0~65535 的字符值,读到末尾返回 -1 |
int read(char[] cbuf) | 将字符读入数组,返回实际读取的字符数 |
abstract int read(char[] cbuf, int off, int len) | 将字符读入数组的指定区间,返回实际读取的字符数 |
long skip(long n) | 跳过 n 个字符 |
boolean ready() | 判断是否可读 |
void close() | 关闭流,释放资源 |
2.2 与 InputStream 的区别
| 维度 | Reader | InputStream |
|---|---|---|
| 数据单位 | 字符(char) | 字节(byte) |
| 字符集 | 支持 Unicode,自动解码 | 不关心字符集 |
| 适用场景 | 文本文件、字符串、字符流 | 图片、音频、二进制文件 |
| 常用实现 | FileReader、BufferedReader | FileInputStream、BufferedInputStream |
3. Reader 的继承体系
Reader的直接子类包括:
BufferedReader:带缓冲的字符输入流,支持按行读取InputStreamReader:字节流到字符流的桥梁,可指定字符集FileReader:读取文件的字符流(InputStreamReader的子类)CharArrayReader:从字符数组读取StringReader:从字符串读取PipedReader:管道字符流,用于线程间通信
4. 常用实现类详解
4.1 FileReader
FileReader用于读取文件中的字符数据,适合读取文本文件。
importjava.io.FileReader;importjava.io.IOException;publicclassFileReaderDemo{publicstaticvoidmain(String[]args){try(FileReaderreader=newFileReader("test.txt")){intch;while((ch=reader.read())!=-1){System.out.print((char)ch);}}catch(IOExceptione){e.printStackTrace();}}}4.2 BufferedReader
BufferedReader为字符流提供缓冲功能,显著提升读取效率,并提供了readLine()方法,可以按行读取文本。
importjava.io.BufferedReader;importjava.io.FileReader;importjava.io.IOException;publicclassBufferedReaderDemo{publicstaticvoidmain(String[]args){try(BufferedReaderreader=newBufferedReader(newFileReader("test.txt"))){Stringline;while((line=reader.readLine())!=null){System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}}}4.3 InputStreamReader
InputStreamReader是字节流到字符流的桥梁,可以指定字符集解码字节数据。
importjava.io.FileInputStream;importjava.io.InputStreamReader;importjava.io.BufferedReader;importjava.io.IOException;importjava.nio.charset.StandardCharsets;publicclassInputStreamReaderDemo{publicstaticvoidmain(String[]args){try(BufferedReaderreader=newBufferedReader(newInputStreamReader(newFileInputStream("test.txt"),StandardCharsets.UTF_8))){Stringline;while((line=reader.readLine())!=null){System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}}}4.4 StringReader
StringReader将字符串作为字符流的数据源,常用于解析字符串内容。
importjava.io.StringReader;publicclassStringReaderDemo{publicstaticvoidmain(String[]args){Stringcontent="Hello, Java Reader!";try(StringReaderreader=newStringReader(content)){intch;while((ch=reader.read())!=-1){System.out.print((char)ch);}}catch(IOExceptione){e.printStackTrace();}}}5. 实战:读取文件并统计字符
下面通过一个完整的示例,演示如何使用Reader读取文件并统计字符数、行数。
importjava.io.BufferedReader;importjava.io.FileReader;importjava.io.IOException;publicclassFileStats{publicstaticvoidmain(String[]args){StringfilePath="test.txt";intcharCount=0;intlineCount=0;try(BufferedReaderreader=newBufferedReader(newFileReader(filePath))){Stringline;while((line=reader.readLine())!=null){lineCount++;charCount+=line.length();}}catch(IOExceptione){e.printStackTrace();}System.out.println("总行数: "+lineCount);System.out.println("总字符数: "+charCount);}}6. 性能对比与优化建议
6.1 单字符读取 vs 缓冲读取
- 单字符读取(
read())每次调用都会触发底层 I/O 操作,性能较差。 - 使用
BufferedReader后,底层会一次性读取大量数据到缓冲区,大幅减少 I/O 次数。
6.2 指定字符集
使用FileReader时默认使用平台默认字符集,跨平台时可能出现乱码。建议使用InputStreamReader并显式指定字符集(如UTF-8)。
6.3 使用 try-with-resources
Java 7 引入的 try-with-resources 语法可以自动关闭流,避免资源泄漏。
7. 常见坑点
7.1 乱码问题
FileReader使用平台默认字符集,在 Windows 上默认可能是 GBK,读取 UTF-8 文件会乱码。解决方案是使用InputStreamReader指定UTF-8。
7.2 read() 返回 -1 的判断
read()返回int类型,读到末尾返回 -1。注意不要将返回值直接强转为char再判断,否则 -1 会被截断为 65535。
7.3 忘记关闭流
忘记关闭流会导致文件句柄泄漏。务必使用 try-with-resources 或在 finally 中关闭。
8. 总结
Reader是 Java 字符输入流的基石,掌握它的继承体系、常用实现类以及性能优化技巧,是写出健壮文本处理代码的关键。在实际开发中,推荐优先使用BufferedReader配合InputStreamReader并显式指定字符集,以获得最佳的性能与兼容性。