你写好一段 java 读取文件的代码,运行时却抛 FileNotFoundException,或者读出来的内容是空的——这多半不是逻辑错,而是路径写错,或用了过时的 FileInputStream 却忘了关流。最稳妥的写法是 Java 7 之后提供的 Files.readAllBytes(Path),一行就能把整个文件读进内存,再用 new String(bytes, StandardCharsets.UTF_8) 转成文本。本文基于 JDK 17 实测,系统梳理 Files.readAllBytes、BufferedReader 逐行读、Files.lines 流式读、以及 Scanner 四种做法,并附编码坑、大文件处理、资源关闭三个实战要点。读完你既能按场景选对方法,也懂什么时候该换方案。今天这篇文章,编程狮就把这块讲透。

一、为什么读取文件总踩坑
Java 读文件表面简单,实则有两个高频雷区。第一是相对路径基准:new File("data/demo.txt") 里的 data 是「当前工作目录」下的子目录,在 IDE 里跑和打包成 jar 后跑,工作目录往往不同,路径自然就找不到了。第二是字符编码:不显式指定编码时,Java 会用平台默认编码(Windows 是 GBK),读一个 UTF-8 文件必然乱码。
版本提醒:JDK 7 引入的 java.nio.file.Files 是最推荐的入口,老代码的 FileInputStream 容易忘记 close 导致文件句柄泄漏。现代项目应优先用 NIO。
⚠️ 注意:不要手动拼接路径字符串。
new File("data/" + name)在 Linux 上会失败,要用Paths.get("data", name)这种平台无关写法,跨环境才不会出事。
1.1 触发条件
什么情况一定会翻车:在 IDE 里能跑、打成 jar 就找不到文件(资源该用 getResourceAsStream);读中文内容出现「锘」或乱码方块;循环里反复 new FileInputStream 没关闭,跑久了报「Too many open files」;读的是 classpath 里的资源却用了文件系统路径。
1.2 常见误解
有人认为「read() 一次就能拿全部内容」。不会。InputStream.read() 单次只读一个字节或一段缓冲,必须循环累加。想一次拿全文本,直接用 Files.readAllBytes 最省心,它内部已经帮你做好了循环读取。
再说一个隐蔽坑:相对路径的基准在不同运行方式下会变。在 IDE 里右键运行,工作目录通常是项目根;用 java -jar 跑,工作目录是你执行命令的目录;放进定时任务(cron)里,工作目录又可能是用户家目录。同一个 Paths.get("data/demo.txt") 三种环境三种结果,这就是「本地能跑、上线就找不到」的根源。稳妥做法是:明确区分「外部文件」(用绝对路径或配置指定)和「打包内资源」(用 getResourceAsStream)。另一个坑是权限:在 Linux 上读 /var/log 或别的用户目录,可能抛 AccessDeniedException,和「文件不存在」是两回事,排错时别只看 FileNotFoundException。还有字符集,Windows 默认 GBK、Linux 默认 UTF-8,跨平台读文本一定要显式传 StandardCharsets.UTF_8,否则同一份文件两边看到的内容不一样。
二、方法一:Files.readAllBytes(最省心)
适合谁:小到中等文件(几 MB 内)、只想拿到完整字符串。代价:会把整个文件读进内存,大文件会触发 OutOfMemoryError,不适合日志类大文本。
- 用
Paths.get构造路径,避免手动拼分隔符; - 调
Files.readAllBytes(path)一次拿 byte 数组; - 用
StandardCharsets.UTF_8显式转码,杜绝平台默认编码。
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
Path path = Paths.get("data", "demo.txt");
byte[] bytes = Files.readAllBytes(path);
String text = new String(bytes, StandardCharsets.UTF_8);
System.out.println(text);
上面这段做的是把 data/demo.txt 整个读进 bytes,再按 UTF-8 解码成字符串。执行后你会看到文件原样打印出来。系统入门建议先过一遍 Java 教程 的 IO 章节打底。
三、方法二:BufferedReader 逐行读(内存友好)
适合谁:文件较大、要逐行处理(如日志分析、CSV 清洗)。代价:代码比方法一多几行,但换来的是恒定内存占用。
BufferedReader 带缓冲,每次读一行,不会一次性占满内存,是处理大文本的经典写法。配合 try-with-resources 还能自动关流。
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 逐行处理
}
}
语法细节记不住时,翻 Java 速查手册 的 IO 段落最快。这里的 try-with-resources 会自动关流,比手写 finally 更安全,也避免了句柄泄漏。
四、方法三:Files.lines 流式读(函数式)
适合谁:要对行做过滤、计数、收集(如「统计 ERROR 行数」)。代价:它是惰性流,忘了关流仍有句柄风险,建议用 try-with-resources 包起来。
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
long count = lines.filter(l -> l.contains("ERROR")).count();
System.out.println("ERROR 行数:" + count);
}
它返回的是 Stream<String>,可以链式 map/filter,写统计类逻辑特别顺手。注意流必须关闭,否则底层文件句柄会一直占用。
五、方法四与四种方法横向对比
Scanner 是第四种,适合「边读边按分隔符解析」,比如逐段读取并用正则切分,但性能是四种里最弱的,仅推荐在解析简单格式时用。
| 方法 | 适用场景 | 优点 | 代价 |
|---|---|---|---|
| Files.readAllBytes | 小文件拿全文本 | 一行搞定 | 大文件内存爆 |
| BufferedReader | 大文件逐行 | 内存友好 | 代码略多 |
| Files.lines | 行级统计/过滤 | 函数式优雅 | 需包 try 关流 |
| Scanner | 边读边解析 | 能按分隔符切 | 性能最弱 |
图:终端里四种读法输出对比(additive,待人工补图)
踩坑清单:
- 编码必须显式指定:永远传
StandardCharsets.UTF_8,别依赖平台默认。 - 大文件别用 readAllBytes:超过几百 MB 改用 BufferedReader 或分页读。
- 资源要关闭:优先 try-with-resources,避免句柄泄漏拖垮进程。
- 资源文件用 getResourceAsStream:jar 内的文件不能用
Paths.get读。
实战里还有「边读边写」的场景要小心:比如把 A 文件内容加工后写入 B 文件,务必先确认 B 的父目录存在(Files.createDirectories),否则会抛 NoSuchFileException。读取大目录时,Files.walk 会递归遍历,若目录极深或含符号链接环,可能栈溢出或死循环,记得用 Files.walkFileTree 配合 SimpleFileVisitor 控制行为。异常处理上,IO 操作几乎都抛受检异常 IOException,别用空 catch 吞掉——至少打日志并给出可读的错误信息,否则生产环境文件读失败时你连原因都查不到。最后提醒一句:读网络挂载的文件(NFS / 云盘)比本地磁盘慢且更易超时,关键路径加超时和重试,比盲目重试整个任务更稳。

总结
Java 读文件,小文件用 Files.readAllBytes 最省心,大文件用 BufferedReader 逐行最稳,Files.lines 适合做行级统计。核心是路径用 Paths.get、编码显式 UTF-8、资源用 try-with-resources 关闭。想深入 JVM 层面的文件与流机制,可以看 Java 虚拟机教程 补充底层知识。
要点带走:
- 相对路径以工作目录为基准,跨环境要用
getResourceAsStream读资源; - 四种写法按内存/场景取舍,编码和关流是两个最常翻车的点;
- 大文件放弃 readAllBytes,逐行或流式处理最稳。
下一步想把 Java 这块学扎实,建议把四种写法都在本地跑一遍:先读个小文件看正常输出,再故意传个错路径记下抛的是什么异常,然后换个含中文的文件试试不指定编码会乱成什么样——亲手踩过一轮,比只看记得牢。
延伸学习
想把这块知识系统补齐,可以按这个顺序来:
- 先过一遍 Java 文件读写配套课程,把基础概念铺平;
- 语法记不住时,翻 Java 入门学习路线笔记 看学习路线加深印象;
- 写完后想随手验证代码,在线运行 Java 代码工具 能直接在浏览器里跑 Java 片段。
常见问题
Q:读 resources 下的文件为什么找不到?
A:打包后文件在 jar 里,不能用 Paths.get 读。要用 getClass().getResourceAsStream("/demo.txt") 以流方式读取,这是 Java 资源加载的固定写法,路径以斜杠开头表示从 classpath 根找。
Q:中文读出来是乱码怎么办?
A:九成是没指定编码。所有读方法都显式传 StandardCharsets.UTF_8,不要依赖平台默认(Windows 默认 GBK 必乱码)。如果源文件本身是 GBK,就要用 StandardCharsets.GBK 去读,编码必须和文件实际编码一致。
Q:几百 MB 的大文件怎么读不爆内存?
A:放弃 readAllBytes,改用 BufferedReader.readLine() 逐行处理,或 Files.lines() 流式消费,处理完一行释放一行,内存占用恒定,不会随文件增大而上涨。

免费 AI IDE



