17_Java 文件IO与NIO
Path、Files、编码、流式处理、上传下载与路径安全。
Java 文件 IO 与 NIO
学习目标:正确读写文本文件,区分小文件一次性读取和大文件流式处理,并防范路径越界。
1. Path 和 Files
Path 表示文件路径,Files 提供读取、写入、复制和遍历等操作。明确字符编码,通常使用 UTF-8。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws IOException {
Path file = Path.of("message.txt");
Files.writeString(file, "你好\n", StandardCharsets.UTF_8);
System.out.println(Files.readString(file, StandardCharsets.UTF_8));
}
}
readString 会把整个文件读入内存,适合小文件。大日志、上传或导出应流式处理,并设置大小、超时与资源限制。Files.lines(path) 返回需要关闭的流,应放在 try-with-resources 中。
2. 二进制与网络文件
文本和字节不能混用:图片、压缩包等二进制文件用字节流;文本需显式编码。NIO 的 Channel/Buffer 适合特定高吞吐或随机访问场景,但普通文件操作先用 Files API 即可。
3. 上传下载安全
用户提供的文件名不能直接拼到存储根路径。先决定允许的文件类型和大小,生成服务端文件名,再确认规范化后的路径仍在允许目录内。仅检查 .. 字符串不够;符号链接等情况也要纳入部署环境的安全设计。
4. 写入与失败恢复
覆盖重要文件前考虑备份或先写临时文件,再使用 Files.move 替换。原子移动能力取决于文件系统和源目标位置,不能假定所有环境都支持。写入失败时应保留可诊断日志,并避免留下半成品被当作有效文件。
自测
- 何时不宜用
Files.readString?文件可能很大、无法合理全部放入内存时。 - 为什么上传文件不能直接使用客户端文件名作为服务器路径?可能发生路径越界、覆盖或名称冲突。
5. 流式读取与资源所有权
大文件不宜用 readString 一次读进内存。逐行读取适合文本日志;二进制复制可用 InputStream/OutputStream 或 Files.copy。逐行处理时必须关闭由 Files.lines 返回的 Stream:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
class Lines {
static long countErrors(Path path) throws IOException {
try (var lines = Files.lines(path, StandardCharsets.UTF_8)) {
return lines.filter(line -> line.contains("ERROR")).count();
}
}
}
编码错误、权限不足和文件被其他进程修改都可能让操作失败;调用方必须知道如何报告或重试。Files.lines 懒读取期间的 I/O 错误可能被包装为未检查异常,不能只假设打开成功后就不会失败。
6. 安全写入与上传边界
覆盖重要配置时,先写同目录临时文件,再尝试 Files.move 替换;是否支持原子替换依赖文件系统。用户上传文件要有大小限制、内容类型验证、服务端生成的存储名和单独的访问权限。路径规范化检查本身不能完全解决符号链接与检查后替换的竞态;需要结合目录权限和安全的文件打开策略。
练习:实现一个限制最大字节数的文本导入函数,分别测试 UTF-8 非法字节、空文件、超大文件和只读目录。