FileWriter 是 Java 中写入字符文件最直接的类,但使用不当容易遇到乱码和性能瓶颈,正确选择缓冲流并指定编码才是关键。
FileWriter 写入中文乱码的根本原因和解决办法
很多初学者在第一次使用 FileWriter 写入中文时,打开文件看到一堆乱码,这通常不是 FileWriter 本身的问题,而是它默认使用系统平台的字符编码(Windows 上的 GBK,Linux 上的 UTF-8),导致跨平台或需要 UTF-8 编码时出现乱码。参考2
编码不一致是乱码的根源
FileWriter 继承自 OutputStreamWriter,但它的构造函数没有提供指定编码的参数,举个例子,在 Windows 上跑程序,FileWriter 默认用 GBK 写文件;如果项目要求统一用 UTF-8,或者把文件部署到 Linux 服务器上读取,编码就不匹配了,乱码自然出现。
解决方案:使用 OutputStreamWriter 显式指定编码
最直接的办法是放弃 FileWriter 的快捷方式,改用 OutputStreamWriter 包装一个 FileOutputStream,并传入编码名称,操作路径如下:
- 步骤1:创建一个
FileOutputStream指向目标文件。 - 步骤2:将
FileOutputStream包装进OutputStreamWriter,构造函数第二个参数写上"UTF-8"(或你需要的编码)。 - 步骤3:可以考虑再包装一层
BufferedWriter提升性能(见下文)。
示例代码核心行:
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8"));
这样写出来的文件编码就是 UTF-8,不会再出现乱码,如果你坚持要用 FileWriter,也有取巧的办法:在 JVM 启动参数中加上 -Dfile.encoding=UTF-8,但这会改变整个程序默认编码,不推荐在生产环境用。参考2
FileWriter 和 BufferedWriter 的区别对比
很多开发者纠结 FileWriter 和 BufferedWriter 到底选哪个,其实它们不是替代关系,而是配合关系,FileWriter 负责底层字节到字符的转换,BufferedWriter 在它上面加了一层缓冲。
性能对比:单次写入 vs 批量写入
| 场景 | FileWriter 直接写入 | BufferedWriter + FileWriter |
|---|---|---|
| 写入少量数据(如几行日志) | 可以接受,没有额外开销 | 稍慢,因为多了缓冲对象创建 |
| 频繁写入大量数据(如文件导出) | 每次写入都触发磁盘操作,性能差 | 缓冲到一定大小再批量写入,性能提升明显 |
| 需要整行写入(write + newLine) | 需要手动拼接换行符 | 提供 newLine() 方法,跨平台兼容 |
行业共识认为,FileWriter 适合写入低频、小数据量;如果循环写入超过几百次,就应该用 BufferedWriter 包装,代码示例:
// 不推荐:循环中反复构造 FileWriter 写入
for (String line : lines) {
FileWriter fw = new FileWriter("log.txt", true);
fw.write(line + "n");
fw.close();
}
// 推荐:用 BufferedWriter 缓冲
try (BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt", true))) {
for (String line : lines) {
bw.write(line);
bw.newLine();
}
}
日常开发中的选择建议
- 如果你只需要写一行配置,且不关心编码,用 FileWriter 最省事。
- 如果你要写一个几十万行的 CSV 导出,务必用 BufferedWriter 包装,否则速度差几倍。
- 如果你要指定编码,直接放弃 FileWriter,用 OutputStreamWriter + FileOutputStream + BufferedWriter 组合。
FileWriter 追加写入与覆盖写入的操作要点
FileWriter 的构造函数第二个参数控制“追加”还是“覆盖”,这个参数是 boolean append,默认 false(覆盖),很多人在需要追加时忘记设成 true,结果每次运行都把旧文件清空了。
追加写入的构造方法
FileWriter fw = new FileWriter("data.log", true);
true 表示在文件末尾追加,false 或省略表示从头覆盖,操作路径很简单,但要注意两点:参考2
- 如果文件不存在,无论
append是true还是false,都会自动创建新文件。 - 多线程或分布式环境下,直接追加写入可能造成数据交错,需要加锁或改用日志框架。
覆盖写入的常见坑
部分开发者误以为每次 new FileWriter 都会清空文件,其实只有构造函数时才会清空,如果你在同一个 writer 对象上多次调用 write()会连续写入,不会覆盖前面的内容,只有重新创建 FileWriter 且 append=false 时才触发覆盖。
FileWriter 的替代方案与性能优化
除了前面提到的 BufferedWriter,FileWriter 还有几个值得关注的替代品和优化点。
FileWriter 与 FileOutputStream 的区别
- FileWriter 是字符流,直接写字符串,自动做编码转换。
- FileOutputStream 是字节流,写原始字节,不做编码转换。
如果你需要写二进制数据(如图片、压缩包),必须用 FileOutputStream;如果写文本,FileWriter 更方便,但 FileWriter 的编码转换依赖系统默认编码,这恰恰是它最大的弱点,许多团队在项目中禁用 FileWriter,强制使用 OutputStreamWriter 指定编码。
使用 try-with-resources 自动关闭资源
无论哪种 writer,都要记得关闭资源,Java 7 引入的 try-with-resources 是最优雅的方式:
try (FileWriter fw = new FileWriter("output.txt")) {
fw.write("hello");
}
这样无论是正常结束还是抛出异常,资源都会被自动关闭,手动在 finally 里 close 已经过时了。
性能优化:关闭自动刷新与手动 flush
BufferedWriter 默认是带缓冲的,写满缓冲区或调用 flush() 才会实际写入磁盘,如果你需要实时写入(比如日志系统),可以手动调用 flush(),但会牺牲部分性能,大多数情况下,让缓冲区自然写满再刷盘效率最高。
FileWriter 使用常见问题
FileWriter 写入文件时如何指定编码?
FileWriter 本身无法直接指定编码,你需要改用 `OutputStreamWriter` 包装 `FileOutputStream`,并在构造函数第二个参数传入编码名称,`”UTF-8″`,这是 Java 官方推荐的做法,也是行业内解决乱码的标准方案。
FileWriter 和 FileOutputStream 有什么区别?
FileWriter 是字符流,适合写入文本,会自动进行编码转换;FileOutputStream 是字节流,适合写入二进制数据,不处理编码,如果你需要写入文本且希望控制编码,应该用 `OutputStreamWriter` 包装 `FileOutputStream`,而不是直接用 FileWriter。
FileWriter 写入大量数据时性能差怎么办?
直接使用 FileWriter 逐条写入,每次操作都会触发系统调用,性能较差,解决方案是包装一层 `BufferedWriter`,它会将数据缓冲到内存中,达到一定大小再批量写入磁盘,对于几十万行以上的写入任务,性能可以提升数倍。
FileWriter 是入门级工具,但生产环境中更推荐组合使用 OutputStreamWriter 与 BufferedWriter,既可控编码又兼顾性能。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/528889.html



