2. 概述
本文将介绍Java中复制文件的几种常见方法。我们将依次探讨:
- 传统IO API(JDK7之前)
- NIO.2 API(JDK7+)
- Apache Commons IO库
- Guava库
每种方案都会提供完整的代码示例和关键注意事项,帮助开发者根据实际场景选择最合适的实现方式。
3. 传统IO API(JDK7之前)
在JDK7之前,使用java.io
复制文件需要手动处理流操作,步骤相对繁琐:
@Test
public void givenIoAPI_whenCopied_thenCopyExistsWithSameContents()
throws IOException {
File copied = new File("src/test/resources/copiedWithIo.txt");
try (
InputStream in = new BufferedInputStream(
new FileInputStream(original));
OutputStream out = new BufferedOutputStream(
new FileOutputStream(copied))) {
byte[] buffer = new byte[1024];
int lengthRead;
while ((lengthRead = in.read(buffer)) > 0) {
out.write(buffer, 0, lengthRead);
out.flush();
}
}
assertThat(copied).exists();
assertThat(Files.readAllLines(original.toPath())
.equals(Files.readAllLines(copied.toPath())));
}
这种实现方式存在明显缺点:
- ✅ 兼容性极好(支持所有Java版本)
- ❌ 代码冗长,需要手动管理缓冲区
- ❌ 性能相对较低(依赖用户空间缓冲)
4. NIO.2 API(JDK7+)
JDK7引入的NIO.2 API通过操作系统级别的优化大幅提升了文件操作性能。核心方法Files.copy()
支持多种复制选项:
4.1 关键复制选项
选项 | 说明 |
---|---|
REPLACE_EXISTING |
覆盖已存在的文件 |
COPY_ATTRIBUTES |
复制文件元数据 |
NOFOLLOW_LINKS |
不跟随符号链接 |
4.2 基础实现示例
@Test
public void givenNIO2_whenCopied_thenCopyExistsWithSameContents()
throws IOException {
Path copied = Paths.get("src/test/resources/copiedWithNio.txt");
Path originalPath = original.toPath();
Files.copy(originalPath, copied, StandardCopyOption.REPLACE_EXISTING);
assertThat(copied).exists();
assertThat(Files.readAllLines(originalPath)
.equals(Files.readAllLines(copied)));
}
⚠️ 注意:目录复制是浅层复制,不会递归处理子目录和文件。
4.3 性能优势
NIO.2的性能提升主要来自:
- 直接使用操作系统提供的零拷贝机制
- 减少用户空间和内核空间的数据拷贝
- 更高效的缓冲区管理
5. Apache Commons IO
对于需要简化代码的场景,Apache Commons IO提供了优雅的解决方案:
5.1 依赖配置
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
5.2 使用示例
@Test
public void givenCommonsIoAPI_whenCopied_thenCopyExistsWithSameContents()
throws IOException {
File copied = new File(
"src/test/resources/copiedWithApacheCommons.txt");
FileUtils.copyFile(original, copied);
assertThat(copied).exists();
assertThat(Files.readAllLines(original.toPath())
.equals(Files.readAllLines(copied.toPath())));
}
✅ 优势:
- 单行代码完成复制
- 内置缓冲区优化
- 自动处理资源关闭
6. Guava
Google的Guava库同样提供了简洁的文件复制方案:
6.1 依赖配置
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
6.2 使用示例
@Test
public void givenGuava_whenCopied_thenCopyExistsWithSameContents()
throws IOException {
File copied = new File("src/test/resources/copiedWithGuava.txt");
com.google.common.io.Files.copy(original, copied);
assertThat(copied).exists();
assertThat(Files.readAllLines(original.toPath())
.equals(Files.readAllLines(copied.toPath())));
}
✅ 特点:
- API设计简洁直观
- 与Guava生态无缝集成
- 内置异常处理优化
7. 方案对比
方案 | 适用场景 | 性能 | 代码复杂度 |
---|---|---|---|
传统IO | 遗留系统维护 | 低 | 高 |
NIO.2 | 现代Java应用 | 高 | 中 |
Commons IO | 快速开发 | 中 | 低 |
Guava | 已使用Guava的项目 | 中 | 低 |
8. 最佳实践建议
- 优先使用NIO.2:JDK7+项目首选,性能最优
- 第三方库选择:
- 已使用Commons IO → 继续用
FileUtils.copyFile()
- 已使用Guava → 选择
Files.copy()
- 已使用Commons IO → 继续用
- 大文件处理:
- 使用NIO.2的
FileChannel
.transferTo() - 避免在内存中缓冲整个文件
- 使用NIO.2的
- 异常处理:
- 明确捕获
IOException
- 考虑添加重试机制(特别是网络文件系统)
- 明确捕获
完整代码示例可参考:GitHub仓库