Reader和Writer是Java字符流顶层抽象类,不可实例化,子类如FileReader、InputStreamReader等负责具体操作;其核心价值是自动处理编码转换,需显式指定Charset避免乱码,推荐用InputStreamReader+FileInputStream或Files.newBufferedReader等带编码参数的方式。

Reader和Writer是Java字符流的顶层抽象类,不是直接拿来用的
它们本身不能实例化,真正干活的是子类,比如 FileReader、InputStreamReader、BufferedReader,以及对应的 FileWriter、OutputStreamWriter、BufferedWriter。直接 new Reader 或 Writer 会编译报错。
字符流的核心价值是自动处理编码转换:底层字节流(如 InputStream)只管读字节,而 Reader 在读取时会按指定字符集(如 UTF-8)把字节解码成 char;Writer 则把 char 编码成字节再写出去。
- 不指定编码时,
FileReader/FileWriter默认用系统默认编码(Windows 是 GBK,Linux/macOS 通常是 UTF-8),极易导致乱码 - 推荐显式使用
InputStreamReader+FileInputStream组合,或OutputStreamWriter+FileOutputStream,这样能传入Charset对象控制编码 -
BufferedReader的readLine()方法能按行读,但不识别\r\n和\n的平台差异——它只把换行符当作分隔符,不保留也不转换
常见乱码场景:FileReader读UTF-8文件在Windows上显示为问号
这是因为 FileReader 硬编码使用系统默认字符集,而 Windows 控制台和记事本默认用 GBK 解析 UTF-8 字节,结果每个中文被拆成两个无效字节,显示为 或 ?。
修复方式很简单:不用 FileReader,改用带编码的构造:
立即学习“Java免费学习笔记(深入)”;
Reader reader = new InputStreamReader(
new FileInputStream("data.txt"),
StandardCharsets.UTF_8
);
- 同理,写文件别用
FileWriter,用new OutputStreamWriter(new FileOutputStream("out.txt"), StandardCharsets.UTF_8) - 如果用
Files.newBufferedReader(Paths.get("..."), StandardCharsets.UTF_8)更简洁,且自动支持 try-with-resources - 注意
StandardCharsets.UTF_8是常量,比字符串"UTF-8"更安全(避免拼写错误和异常)
BufferedReader.readLine() 返回 null 表示流已结束,不是空行
readLine() 读到末尾返回 null,读到空行返回空字符串 ""。很多人误把 null 当作内容为空,导致 NPE 或逻辑跳过最后一行。
- 正确判断方式:
String line; while ((line = reader.readLine()) != null) { ... } -
readLine()不包含换行符(\n或\r\n),如需还原原始格式,得自己拼接 - 如果要保留换行符,别用
readLine(),改用read(char[] cbuf)或read()单字符读取
Writer.flush() 和 close() 的区别经常被忽略
flush() 强制把缓冲区里还没写出的字符推送到目标(比如磁盘或网络),但流还开着;close() 先 flush() 再释放资源。如果只 flush() 不 close(),可能造成资源泄漏(尤其涉及文件句柄或 socket)。
- 使用 try-with-resources 是最稳妥的方式:
try (BufferedWriter w = new BufferedWriter(...)) { ... },自动调用close() - 手动管理时,
close()必须在 finally 块中执行,否则异常发生时缓冲区内容可能丢失 -
Writer的write(String)方法不会自动 flush,即使字符串很长也一样——缓冲行为由具体实现决定(如BufferedWriter默认 8192 字节缓冲)
字符流看着比字节流“高级”,但编码、缓冲、关闭时机这几个点一不留神就出问题,尤其是跨平台部署时,系统默认编码差异会让 bug 隐蔽又难复现。









