大文件读写
大文件处理的核心原则是:不要一次性读入内存。大文件读写既要关注内存占用,也要关注吞吐量、磁盘 IO、异常恢复和处理进度。
# 1. 不推荐一次性读取
下面写法只适合小文件:
byte[] bytes = Files.readAllBytes(path);
String content = Files.readString(path, StandardCharsets.UTF_8);
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
如果文件很大,可能导致:
- 内存暴涨。
- Full GC。
OutOfMemoryError。- 服务响应变慢。
# 2. 按块读取二进制文件
try (InputStream input = new BufferedInputStream(Files.newInputStream(source));
OutputStream output = new BufferedOutputStream(Files.newOutputStream(target))) {
byte[] buffer = new byte[1024 * 64];
int len;
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
}
}
缓冲区大小可以根据场景压测调整。常见起点是 8KB、16KB、64KB。
# 3. 按行处理文本大文件
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
process(line);
}
}
这种方式内存稳定,适合日志分析、CSV 导入等场景。
# 4. Stream 方式
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
lines
.filter(line -> !line.isBlank())
.forEach(this::process);
}
Files.lines 是惰性读取,但底层资源需要关闭,所以必须使用 try-with-resources。
# 5. 大文件复制
优先考虑:
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
如果需要更高性能或零拷贝能力:
try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
FileChannel out = FileChannel.open(target,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
long position = 0;
long size = in.size();
while (position < size) {
position += in.transferTo(position, size - position, out);
}
}
# 6. 断点续传思路
断点续传通常需要记录:
- 文件总大小。
- 已处理位置。
- 分片大小。
- 每片校验值。
- 最终文件校验值。
可以使用 RandomAccessFile 或 FileChannel.position 定位写入。
try (RandomAccessFile file = new RandomAccessFile("download.tmp", "rw")) {
file.seek(offset);
file.write(buffer, 0, len);
}
# 7. 大文件处理注意点
- 控制并发数,避免磁盘被打满。
- 不要无限制创建大缓冲区。
- 记录处理进度,方便失败恢复。
- 对关键文件做 checksum 校验。
- 处理临时文件和最终文件的原子切换。
- 避免在主业务线程中长时间同步处理。
# 专家实践清单
| 场景 | 推荐方案 | 风险控制 |
|---|---|---|
| 大二进制复制 | 分块缓冲或 FileChannel | 限制缓冲大小 |
| 大文本处理 | 按行流式读取 | 关闭 Stream |
| 文件上传 | 分片、限速、校验 | 限制大小和类型 |
| 断点续传 | 记录 offset 和 checksum | 校验一致性 |
| 超大随机访问 | mmap 分片 | 管理堆外内存 |
大文件处理思路:
不要整体读入内存
▼
分块读取
▼
逐块处理或写出
▼
记录进度和校验
▼
失败可恢复
# Tips 快问快答
Q:大文件处理第一原则是什么? A:流式处理,不要一次性读入内存。
Q:为什么 readAllBytes 危险?
A:文件过大时会造成堆内存压力甚至 OOM。
Q:缓冲区多大合适? A:从 64KB、256KB 等起点压测,结合磁盘、网络和内存调整。
Q:大文本按行处理要注意什么? A:字符集、行长度异常、Stream 关闭和坏行处理。
Q:断点续传需要记录什么? A:已处理偏移、分片大小、文件标识和校验值。
Q:大文件能不能并行处理? A:可以,但要能按偏移切分,并处理边界和结果合并。
Q:大文件写入如何避免半成品? A:写临时文件,完成校验后原子移动。
Q:大文件处理应该放在请求线程里吗? A:通常不应该,建议异步任务、限流和进度可观测。
# 8. 总结
大文件处理最重要的是流式思维。一次只处理一小段,避免把整个文件塞进内存。性能优化时再根据瓶颈选择缓冲流、FileChannel、零拷贝、内存映射或并行分片。
上次更新: 2026/06/25, 14:19:18