Java实战:复制文件的4 种方法

“复制文件看似简单,却是展示代码功底的好机会!” 🎯 无论是期末作业、毕业设计,还是项目开发,本文带你全面掌握 Java 文件复制的 4 种方法:经典的流(Stream)、高效的 FileChannel、便捷的 Apache Commons IO,以及现代化的 Files 类,外加 1GB 文件性能对比,帮你选出最快、最适合的方案!💡 阅读后别忘了 点赞 👍、收藏 ⭐、关注 👀、分享 🔗,一起提升 Java 技能,让你的代码更出彩、更高效!✨。如果你有更好的方法,欢迎在评论区留言,有任何疑问也可以私信作者,给你解答。


Java 文件复制是一个非常常见的操作。但是 java.io.File 类并没有提供从源文件复制到目标文件的快捷方法。本文中介绍在 Java 中实现文件复制的四种不同方式。
 

一.使用流(Stream)复制文件

这是在 Java 中实现文件复制的传统方式。在这种方法中,我们创建两个文件对象——源文件(source)和目标文件(destination)。然后,我们从源文件创建输入流(InputStream),并通过输出流(OutputStream)将数据写入目标文件。

以下是使用流(Stream)实现文件复制的方法代码

// 使用流(Stream)复制文件的方法
private static void copyFileUsingStream(File source, File dest) throws IOException {
    InputStream is = null; // 定义输入流对象,用于从源文件中读取数据
    OutputStream os = null; // 定义输出流对象,用于将数据写入目标文件
    try {
        // 创建输入流,指向源文件
        is = new FileInputStream(source);
        // 创建输出流,指向目标文件
        os = new FileOutputStream(dest);
        // 定义一个字节缓冲区,用于临时存储读取的数据,大小为 1024 字节
        byte[] buffer = new byte[1024];
        int length; // 定义变量,用于存储每次读取的字节数
        // 循环读取源文件的数据,直到文件结束(read() 返回 -1)
        while ((length = is.read(buffer)) > 0) {
            // 将读取的数据写入目标文件,从缓冲区的起始位置开始写入,写入长度为读取的字节数
            os.write(buffer, 0, length);
        }
    } finally {
        // 关闭输入流,释放资源
        if (is != null) {
            is.close();
        }
        // 关闭输出流,释放资源
        if (os != null) {
            os.close();
        }
    }
}

1.1.方法说明

  1. 输入流和输出流:通过 FileInputStreamFileOutputStream 创建输入和输出流,用于读取源文件和写入目标文件。
  2. 缓冲区byte[] buffer 用于临时存储从源文件中读取的数据,避免频繁操作文件,提高效率。
  3. 循环读取与写入:通过 is.read(buffer) 方法从源文件中读取数据,并通过 os.write(buffer, 0, length) 将数据写入目标文件。
  4. 关闭资源:在 finally 块中关闭流,以确保无论是否发生异常,资源都会被正确释放。

1.2.注意事项

  • 异常处理:代码中使用了 try-finally 块来确保资源的正确释放。
  • 缓冲区大小:缓冲区大小可以根据文件大小和性能需求进行调整,以提高效率。

二.使用java.nio.channels.FileChannel实现文件复制

Java NIO 类是在 Java 1.4 中引入的,其中 FileChannel 可用于在 Java 中复制文件。根据 javadoc中transferFrom() 方法的介绍,这种文件复制方法的速度通常比使用流(Streams)更快。

以下是使用 FileChannel 复制文件的方法实现。

// 使用 FileChannel 实现文件复制
private static void copyFileUsingChannel(File source, File dest) throws IOException {
    FileChannel sourceChannel = null; // 定义源文件的 FileChannel
    FileChannel destChannel = null;  // 定义目标文件的 FileChannel
    try {
        // 创建源文件的 FileChannel,使用 FileInputStream 打开源文件
        sourceChannel = new FileInputStream(source).getChannel();
        
        // 创建目标文件的 FileChannel,使用 FileOutputStream 打开目标文件
        destChannel = new FileOutputStream(dest).getChannel();
        
        // 使用 FileChannel 的 transferFrom 方法从源文件复制内容到目标文件
        // 参数 0 表示从文件的开头开始复制
        // sourceChannel.size() 获取源文件的总大小(字节数)
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
    } finally {
        // 关闭源文件的 FileChannel,释放资源
        if (sourceChannel != null) sourceChannel.close();
        
        // 关闭目标文件的 FileChannel,释放资源
        if (destChannel != null) destChannel.close();
    }
}

2.1.方法说明:

  1. FileChannel

    • java.nio 包中用于文件读写的通道类。
    • 它支持文件的高效读写,尤其适合处理大文件。
  2. transferFrom() 方法

    • 这是 FileChannel 提供的一个方法,用于从一个通道(sourceChannel)复制内容到当前通道(destChannel)。
    • 参数说明:
      • 第一个参数:源文件的通道对象。
      • 第二个参数:目标文件中写入数据的起始位置。
      • 第三个参数:需要复制的字节数。
  3. 代码关键点

    • try 块中初始化资源,并在 finally 块中确保释放资源(关闭通道)。
    • 使用 FileInputStreamFileOutputStream 创建文件输入流和输出流,并从中获取 FileChannel

通过这种方式复制文件,效率较高,适合处理大文件,但具体性能提升与文件大小和硬件配置有关。

三.使用 Apache Commons IO 的 FileUtils 进行文件复制

Apache Commons IO 提供了一个简单的工具方法 FileUtils.copyFile(File srcFile, File destFile) 来实现文件复制。如果你的项目中已经引入了 Apache Commons IO 库,使用它可以使代码更简单明了。

注意FileUtils.copyFile 方法内部使用了 Java NIO 的 FileChannel,因此如果你的项目中没有其他功能依赖 Apache Commons IO,可以直接使用 FileChannel 而没有必要单独引入Apache Commons IO库

以下是使用 Apache Commons IO 进行文件复制的代码示例:

// 使用 Apache Commons IO 的 FileUtils 方法实现文件复制
private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    // 使用 FileUtils.copyFile 方法将源文件复制到目标文件
    FileUtils.copyFile(source, dest);
}

3.1.方法说明

  • File source:表示源文件对象。
  • File dest:表示目标文件对象。
  • FileUtils.copyFile():Apache Commons IO 提供的静态方法,用于文件复制操作。

优点

  1. 代码简单,易于理解。
  2. 如果项目已经依赖 Apache Commons IO,使用该方法是一个方便的选择。
  3. 内部基于 Java NIO 的 FileChannel,具有较高的性能。

缺点

  • 如果项目中没有其他功能需要 Apache Commons IO,仅为了文件复制而引入这个库可能有些多余。

此方法适用于希望通过简单方式实现文件复制且项目已使用 Apache Commons IO 的场景。

四.使用 Files 类实现文件复制

如果你使用的是 Java 7 或更高版本,可以使用 java.nio.file.Files 类的 copy() 方法来复制文件。该方法通过文件系统提供器(File System Providers)执行文件复制操作,非常方便和简洁。

以下是使用 Files 类的 copy() 方法 进行文件复制的代码示例

// 使用 Java 7 的 Files.copy 方法实现文件复制
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    // 使用 Files.copy 方法从源文件的路径 (source.toPath()) 复制数据
    // 到目标文件的路径 (dest.toPath())
    Files.copy(source.toPath(), dest.toPath());
}

4.1.方法说明

  1. source.toPath()

    • 将源文件 File 对象转换为路径对象(Path)。
    • 这是 Files.copy() 方法的输入参数之一。
  2. dest.toPath()

    • 将目标文件 File 对象转换为路径对象(Path)。
    • 用于指定复制操作的目标位置。
  3. 异常处理

    • 方法声明抛出 IOException,表明在复制过程中可能会发生 I/O 异常,例如文件不存在或文件不可读写。

优点

  • 简洁性Files.copy() 是一个一行代码的实现,适合需要快速实现文件复制的场景。
  • 兼容性:适用于 Java 7 及以上版本。
  • 灵活性:支持文件系统的抽象,可以与多种文件系统提供器(例如本地文件系统、虚拟文件系统)一起使用。

如果需要进一步扩展,还可以结合 StandardCopyOption 选项,实现例如覆盖目标文件的功能:

// 使用 Files.copy 方法并指定覆盖选项
Files.copy(source.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);

这个选项会在目标文件已经存在的情况下覆盖目标文件,避免抛出文件已存在的异常。

五.四种方法对比

为了找出哪种方法最快,我编写了一个测试类,并依次对上述方法进行了测试,用它们来复制一个 1 GB 的文件。每次调用时,我都使用了不同的文件,以避免由于缓存导致后续方法受益。

 注意:以下代码中的文件的路径可以改为你自己的文件的路径

// 导入必要的类和包
import java.io.File; // 用于表示文件和目录路径名的抽象表示
import java.io.FileInputStream; // 用于从文件中读取数据的输入流
import java.io.FileOutputStream; // 用于向文件中写入数据的输出流
import java.io.IOException; // 用于处理输入/输出操作中的异常
import java.io.InputStream; // 输入流的通用父类
import java.io.OutputStream; // 输出流的通用父类
import java.nio.channels.FileChannel; // 用于文件通道的复制操作
import java.nio.file.Files; // Java 7 中引入的文件操作类

// 导入 Apache Commons IO 库中的工具类
import org.apache.commons.io.FileUtils; // 提供简单实用的文件操作方法

// 定义主类
public class JavaCopyFile {

    // 主方法,程序从这里开始执行
    public static void main(String[] args) throws InterruptedException, IOException {
        // 定义源文件对象,表示要复制的文件
        File source = new File("/Users/pankaj/tmp/source.avi");
        // 定义目标文件对象,表示复制的目标位置
        File dest = new File("/Users/pankaj/tmp/dest.avi");

        // 使用流 (Stream) 方法复制文件
        long start = System.nanoTime(); // 记录开始时间
        copyFileUsingStream(source, dest); // 调用流方法复制文件
        System.out.println("Time taken by Stream Copy = " + (System.nanoTime() - start)); // 打印流方法的执行时间

        // 使用 FileChannel 方法复制文件
        source = new File("/Users/pankaj/tmp/sourceChannel.avi"); // 定义新的源文件对象
        dest = new File("/Users/pankaj/tmp/destChannel.avi"); // 定义新的目标文件对象
        start = System.nanoTime(); // 记录开始时间
        copyFileUsingChannel(source, dest); // 调用 FileChannel 方法复制文件
        System.out.println("Time taken by Channel Copy = " + (System.nanoTime() - start)); // 打印 FileChannel 方法的执行时间

        // 使用 Apache Commons IO 工具方法复制文件
        source = new File("/Users/pankaj/tmp/sourceApache.avi"); // 定义新的源文件对象
        dest = new File("/Users/pankaj/tmp/destApache.avi"); // 定义新的目标文件对象
        start = System.nanoTime(); // 记录开始时间
        copyFileUsingApacheCommonsIO(source, dest); // 调用 Apache Commons IO 方法复制文件
        System.out.println("Time taken by Apache Commons IO Copy = " + (System.nanoTime() - start)); // 打印 Apache Commons IO 方法的执行时间

        // 使用 Java 7 的 Files 类复制文件
        source = new File("/Users/pankaj/tmp/sourceJava7.avi"); // 定义新的源文件对象
        dest = new File("/Users/pankaj/tmp/destJava7.avi"); // 定义新的目标文件对象
        start = System.nanoTime(); // 记录开始时间
        copyFileUsingJava7Files(source, dest); // 调用 Files 类方法复制文件
        System.out.println("Time taken by Java7 Files Copy = " + (System.nanoTime() - start)); // 打印 Files 类方法的执行时间
    }
}

以下是上述程序的输出结果,每次只使用一种方法进行 Java 文件复制操作,需要把其他方法相关的代码给注释掉。

Time taken by Stream Copy = 44582575000
Time taken by Channel Copy = 104138195000
Time taken by Apache Commons IO Copy = 108396714000
Time taken by Java7 Files Copy = 89061578000

 从输出结果可以看出,流复制(Stream Copy)是 Java 中复制文件的最佳方法。但这只是一个非常基础的测试。如果你正在开发一个对性能要求较高的项目,那么你应该尝试不同的文件复制方法,并记录它们的执行时间,以找到适合你项目的最佳方法。此外,你还可以根据文件的平均大小,尝试不同的 Java 文件复制方式来进行优化。

 

通过这篇文章,相信你已经掌握了 Java 文件复制的 4 种不同实现方式,并且了解了每种方法的适用场景和性能表现。无论是用于期末作业、毕业设计,还是实际项目开发,选择最优的复制方式,都能让你的代码更高效、更专业!💡

如果这篇文章对你有所帮助,不要忘记 点赞 👍、收藏 ⭐、关注 👀,你的支持是我们分享更多优质内容的动力!🔥 同时,欢迎浏览我们的其他文章,探索更多 Java 技术干货和项目优化技巧。💻 学习之路,你我同行,下一篇更精彩!🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大名顶顶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值