Files.newOutputStream() 默认无缓冲,直接写入性能差,需包裹 BufferedOutputStream 并用 try-with-resources 确保关闭;注意编码、换行符、磁盘空间及句柄泄漏。

Files.newOutputStream() 默认不带缓冲,直接写容易变慢
调用 Files.newOutputStream() 返回的是一个裸 OutputStream,底层走的是系统调用,每次 write() 都可能触发一次磁盘 I/O。尤其导出大文件(如 10MB+ 的 CSV/JSON)时,逐字节或小块写入会让性能掉得非常明显——实测在普通 SATA 盘上可能比 Buffered 模式慢 5–10 倍。
它本身不提供缓冲能力,必须手动套一层 BufferedOutputStream 才能生效。
- 别直接用
Files.newOutputStream(path).write(...)循环写小数据 - 务必包裹:
new BufferedOutputStream(Files.newOutputStream(path)) - 缓冲区大小默认是 8192 字节,对大多数场景够用;导出超大文件(GB 级)可显式设为 64KB 或 128KB:
new BufferedOutputStream(out, 65536)
BufferedOutputStream 的 flush() 和 close() 必须配对调用
很多人以为写完就完事了,结果发现文件末尾缺数据、长度不对、甚至为空——这是因为 BufferedOutputStream 把数据攒在内存缓冲区里,没主动刷出去。
close() 会自动 flush(),但前提是没发生异常;一旦写过程抛出 IOException,close() 可能根本没执行到,缓冲区数据就丢了。
- 必须用 try-with-resources,确保
BufferedOutputStream被关闭:try (var out = new BufferedOutputStream(Files.newOutputStream(path))) { out.write(data); } - 不要在循环中频繁
flush()(比如每写一行就 flush),这等于退化回无缓冲模式 - 如果需要实时落盘(如日志导出防崩溃丢数),可在关键断点后调用
out.flush(),但要权衡性能
导出文本数据时,别忽略字符编码和换行符
Files.newOutputStream() 返回的是字节流,不处理编码。如果你用 String.getBytes() 写文本,又没指定 charset,会默认用平台编码(Windows 是 GBK,Linux/macOS 是 UTF-8),导致跨环境乱码。
- 写文本前统一转成 UTF-8 字节:
str.getBytes(StandardCharsets.UTF_8) - 换行符别硬写
"\n"或"\r\n",用System.lineSeparator()更安全 - 如果导出 CSV/TSV,字段含换行或逗号,还得做转义(这不是流的问题,但常一起出错)
大文件导出失败常见于 no space left on device 或 Too many open files
这两个错误看起来像磁盘满或句柄泄漏,其实常因流没关干净引发:
-
Files.newOutputStream()在 Windows 上会独占文件,若未 close 就尝试二次打开,会报AccessDeniedException - 大量并发导出时,每个
BufferedOutputStream占一个文件句柄,忘了关就会触达系统 limit(ulimit -n) - 临时目录空间不足也容易被忽略:
Files.newOutputStream()写的是目标路径,不是 tmpdir;导出前建议先Files.getFileStore(path).getUsableSpace()检查余量


















