在Java中复制文件的最快方法

23

在Java中复制大量文件的最快方法是什么?到目前为止,我使用过文件流和nio。总体而言,流似乎比nio更快。您有什么经验吗?


2
你在复制文件时是否进行了某种转换?为什么不直接使用操作系统的文件系统功能呢? - Mike Daniels
不,我没有进行任何转换。但是当复制10,000多个文件时,错误处理会更加困难,并且在复制小文件时生成系统线程的开销太大了。 - Mato
我建议您修改问题,以包括我们发现的限制。 - Tony Ennis
你想要同时执行多少个任务? - Thorbjørn Ravn Andersen
你尝试过tar管道吗?rsync呢?(系统管理员可能会在这方面提供帮助 - 可能是针对ServerFault的) - Jayan
通常情况下,NIO更快,并且每个驱动器使用一个线程是最优的。 - Peter Lawrey
6个回答

23

http://www.baptiste-wicht.com/2010/08/file-copy-in-java-benchmark/ 可能会为您提供答案。

对于基准测试,我使用了不同的文件进行测试。

  1. 小文件(5 KB)
  2. 中等文件(50 KB)
  3. 大文件(5 MB)
  4. 超大文件(50 MB)
  5. 还有一个巨大的二进制文件(1.3 GB)

我先使用文本文件进行测试,然后使用二进制文件进行测试。我使用三种模式进行测试:

  1. 在同一硬盘上。这是一个带有8 MB缓存的250 GB IDE硬盘。它采用Ext4格式。
  2. 在两个磁盘之间传输。我使用了第一个硬盘和另一个带有16 MB缓存的250 GB SATA硬盘。它采用Ext4格式。
  3. 在两个磁盘之间传输。我使用了第一个硬盘和另一个带有32 MB缓存的1 TB SATA硬盘。它采用NTFS格式。

我使用基准测试框架(在此处描述)对所有方法进行测试。这些测试是在我的个人计算机上进行的(Ubuntu 10.04 64位、Intel Core 2 Duo 3.16 GHz、6 GB DDR2、SATA硬盘)。所使用的Java版本是Java 7 64位虚拟机...


10

我会使用:

import java.io.*;
import java.nio.channels.*;

public class FileUtils{
    public static void copyFile(File in, File out) 
        throws IOException 
    {
        FileChannel inChannel = new
            FileInputStream(in).getChannel();
        FileChannel outChannel = new
            FileOutputStream(out).getChannel();
        try {
            inChannel.transferTo(0, inChannel.size(),
                    outChannel);
        } 
        catch (IOException e) {
            throw e;
        }
        finally {
            if (inChannel != null) inChannel.close();
            if (outChannel != null) outChannel.close();
        }
    }

    public static void main(String args[]) throws IOException{
        FileUtils.copyFile(new File(args[0]),new File(args[1]));
  }
}

如果您在Windows中有任何文件大小超过64M,您可能需要查看这个链接: http://forums.sun.com/thread.jspa?threadID=439695&messageID=2917510


14
捕获 (catch) IOException 异常,并将其抛出 (throw e)。 - unbeli
1
谢谢,这比我之前用的快多了! - Chris.Jenkins
我的IDE警告我应该关闭InputStream而不是通道...对此有什么评论吗? - basZero
@basZero在https://dev59.com/Lmsz5IYBdhLWcg3wLU12#8210815中说,它们都应该可以工作。 - Romain Hippeau
@unbeli catch (IOException e) { throw new RuntimeException(e) } 可以更好 - acrastt
这是样例代码,不是生产代码,10年前编写的。 - Romain Hippeau

2

使用流

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);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
    } finally {
        is.close();
        os.close();
    }
}

使用通道

private static void copyFileUsingChannel(File source, File dest) throws IOException {
    FileChannel sourceChannel = null;
    FileChannel destChannel = null;
    try {
        sourceChannel = new FileInputStream(source).getChannel();
        destChannel = new FileOutputStream(dest).getChannel();
        destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
       }finally{
           sourceChannel.close();
           destChannel.close();
       }
}

使用Apache Commons IO库

private static void copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    FileUtils.copyFile(source, dest);
}

使用Java SE 7文件操作
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

性能测试

File source = new File("/Users/tmp/source.avi");
File dest = new File("/Users/tmp/dest.avi");

//copy file conventional way using Stream
long start = System.nanoTime();
copyFileUsingStream(source, dest);
System.out.println("Time taken by Stream Copy = "+(System.nanoTime()-start));

//copy files using java.nio FileChannel
source = new File("/Users/tmp/sourceChannel.avi");
dest = new File("/Users/tmp/destChannel.avi");
start = System.nanoTime();
copyFileUsingChannel(source, dest);
System.out.println("Time taken by Channel Copy = "+(System.nanoTime()-start));

//copy files using apache commons io
source = new File("/Users/tmp/sourceApache.avi");
dest = new File("/Users/tmp/destApache.avi");
start = System.nanoTime();
copyFileUsingApacheCommonsIO(source, dest);
System.out.println("Time taken by Apache Commons IO Copy = "+(System.nanoTime()-start));

//using Java 7 Files class
source = new File("/Users/tmp/sourceJava7.avi");
dest = new File("/Users/tmp/destJava7.avi");
start = System.nanoTime();
copyFileUsingJava7Files(source, dest);
System.out.println("Time taken by Java7 Files Copy = "+(System.nanoTime()-start));

结果

Time taken by Stream Copy            =  44,582,575,000
Time taken by Java7 Files Copy       =  89,061,578,000
Time taken by Channel Copy           = 104,138,195,000
Time taken by Apache Commons IO Copy = 108,396,714,000

2
最好直接粘贴此链接 https://www.journaldev.com/861/java-copy-file :) - Mak
2
@Mak 粘贴链接并不好,因为链接在未来可能会失效。例如:一个非被接受的答案中的 Oracle 链接已经无法使用了。 - Mirrana

1

这取决于文件(更大的文件),对我来说,使用缓冲流是最快的方式。

 public void copyFile(File inFileStr, File outFileStr) throws IOException {

    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFileStr)); 
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFileStr))) {

        byte[] buffer = new byte[1024 * 1024];
        int read = 0;
        while ((read = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, read);
        }

        bis.close();
        bos.close();
    } catch (IOException ex) {
        ex.printStackTrace();
    }

}

1
你可以使用apache commons-io库中的FileUtils实现来复制文件。
FileUtils.copyFile(new File(sourcePath), new File(destPath));

使用 FileChannel 进行IO操作。

或者使用 java.nio.file.Filescopy() 方法。


1

让Java分叉一个操作系统批处理脚本来复制文件。你的代码可能需要编写批处理脚本。


1
已经考虑过这个问题,但是当复制10,000多个文件时,错误处理会变得非常困难,并且在复制小文件时生成系统线程的开销太大。此外,该应用程序将不具备平台独立性。 - Mato
  1. 无论如何,您都必须处理错误检查。
  2. 您是正确的,这不会是平台独立的 - 所以您打算在不同类型的服务器上运行它?
  3. 您能否一开始就在“适当”的位置创建这10,000个文件,而根本不需要复制呢?
  4. 不要为每个文件生成一个线程。每100个文件或其他数量生成一个线程。
- Tony Ennis
我同意这个观点。愚蠢地复制文件并不是Java的理想用例。如果你想从Java中实现它,那么最好是分叉一个操作系统级别的调用。 - bwawok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接