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. 最佳实践建议

  1. 优先使用NIO.2:JDK7+项目首选,性能最优
  2. 第三方库选择
    • 已使用Commons IO → 继续用FileUtils.copyFile()
    • 已使用Guava → 选择Files.copy()
  3. 大文件处理
    • 使用NIO.2的FileChannel.transferTo()
    • 避免在内存中缓冲整个文件
  4. 异常处理
    • 明确捕获IOException
    • 考虑添加重试机制(特别是网络文件系统)

完整代码示例可参考:GitHub仓库


原始标题:How to Copy a File with Java