Java文件读写需先明确数据类型(字节/字符)、缓冲需求、自动关闭及编码,小文件优先用Files类(JDK7+),大文件或逐块处理须用带缓冲流并指定UTF-8,所有流必须try-with-resources,FileChannel慎用于大文件。

Java 文件读写不是选 FileInputStream 还是 FileWriter 的问题,而是得先分清:你要处理的是字节还是字符、是否需要缓冲、要不要自动关闭、是否涉及中文或大文件——选错底层类,轻则乱码,重则内存溢出或资源泄漏。
用 Files 类做简单读写(JDK7+ 推荐)
90% 的日常需求——读一个配置文件、写一段日志、复制小文本——直接用 Files 静态方法最安全省心,它自动处理编码、关闭流、路径解析:
-
Files.readAllLines(Paths.get("a.txt"), StandardCharsets.UTF_8)返回List,适合按行读小文本;注意别用在几百 MB 的日志上,会 OOM -
Files.writeString(Paths.get("b.txt"), "你好", StandardCharsets.UTF_8)(JDK11+)或Files.write(..., StandardCharsets.UTF_8)(JDK7+)写入字符串,自动覆盖,不追加 - 追加写需显式传
StandardOpenOption.APPEND,否则默认清空重写 - 路径含中文或空格完全没问题,
Paths.get()比拼接字符串更可靠
需要逐块处理时,必须用带缓冲的流
读写超过几 MB 的文件,或要边读边解析(比如 CSV、日志过滤),就不能用 Files.readAllBytes() 一类全量加载的方法:
- 字节场景(图片、ZIP、二进制):用
BufferedInputStream包装FileInputStream,缓冲区默认 8KB,够用;自己 newbyte[8192]手动 read 也行,但记得检查read()返回值是否为 -1 - 字符场景(文本、JSON、XML):必须用
InputStreamReader+BufferedReader,且显式指定StandardCharsets.UTF_8;别依赖平台默认编码,Windows 默认 GBK,Linux 是 UTF-8,不指定必乱码 - 写入同理:
OutputStreamWriter+BufferedWriter,别用裸FileWriter(它不接受 charset 参数)
try-with-resources 不是可选项,是强制项
所有实现 AutoCloseable 的流(FileInputStream、BufferedReader、FileChannel 等)都必须用 try-with-resources,否则容易泄露文件句柄:
立即学习“Java免费学习笔记(深入)”;
- 错误写法:
BufferedReader br = new BufferedReader(...); br.read(); br.close();—— 如果read()抛异常,close()就不会执行 - 正确写法:
try (BufferedReader br = Files.newBufferedReader(path, UTF_8)) { ... },括号内声明,JVM 保证退出时调用close() - 多个资源可写在同一 try 括号里,用分号隔开,关闭顺序与声明顺序相反
大文件或高并发场景下,FileChannel 和 MappedByteBuffer 要慎用
有人看到“NIO 快”就直接上 FileChannel.map(),结果线上频繁 Full GC 或报 OutOfMemoryError: Map failed:
-
MappedByteBuffer占用的是堆外内存,不受-Xmx控制,但受系统虚拟内存和-XX:MaxDirectMemorySize限制 - 映射整个 GB 级文件极易触发 OS 内存压力,尤其在容器环境(如 Docker 默认限制 64MB direct memory)
- 普通读写用
FileChannel.transferTo()/transferFrom()更稳妥,它利用零拷贝,比流复制快,且不额外占堆外内存 - 除非你真在做随机访问百万级小记录(比如数据库索引文件),否则别轻易 mmap
真正难的不是写对一行代码,而是判断当前场景属于哪一类:是脚本式小文件操作?还是服务中持续读写日志?又或是离线处理 TB 级数据?选型偏差一毫米,上线后就是 CPU 突增或磁盘 IO 卡死。编码前多花三十秒想清楚数据规模、字符集、错误恢复要求,比查十次 API 文档更管用。










