一直困扰我的是,在Java中复制文件的唯一方法包括打开流,声明缓冲区,读入一个文件,循环遍历它,并将其写入到另一个steam。网络上充斥着类似的,但仍然略有不同的这类解决方案的实现。

是否有一种更好的方法能够保持在Java语言的范围内(即不涉及执行特定于操作系统的命令)?也许在一些可靠的开源实用程序包中,它至少会模糊这个底层实现,并提供一行解决方案?


当前回答

在Java 7中作为标准提供,path.copyTo: http://openjdk.java.net/projects/nio/javadoc/java/nio/file/Path.html http://java.sun.com/docs/books/tutorial/essential/io/copy.html

我不敢相信他们花了这么长时间来标准化像文件复制这样常见而简单的事情:(

其他回答

请注意,所有这些机制都只复制文件的内容,而不是权限等元数据。因此,如果您要复制或移动linux上的可执行.sh文件,新文件将不能执行。

为了真正地复制或移动文件,即获得与从命令行复制相同的结果,实际上需要使用本地工具。shell脚本或JNI。

显然,这可能会在java 7 - http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html中修复。祈祷!

上述代码有三个可能的问题:

如果getChannel抛出异常,则可能泄漏一个开放流。 对于大文件,您可能会尝试一次传输超出操作系统处理能力的文件。 您忽略了transferFrom的返回值,因此它可能只是复制文件的一部分。

这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂。还要注意,虽然transferFrom没问题,但transferTo会在Linux上的JDK 1.4上中断(参见Bug ID:5056395)

public static void copyFile(File src, File dst) throws IOException
{
    long p = 0, dp, size;
    FileChannel in = null, out = null;

    try
    {
        if (!dst.exists()) dst.createNewFile();

        in = new FileInputStream(src).getChannel();
        out = new FileOutputStream(dst).getChannel();
        size = in.size();

        while ((dp = out.transferFrom(in, p, size)) > 0)
        {
            p += dp;
        }
    }
    finally {
        try
        {
            if (out != null) out.close();
        }
        finally {
            if (in != null) in.close();
        }
    }
}

虽然有点晚了,但是这里比较了使用各种文件复制方法复制一个文件所花费的时间。我循环使用这些方法10次,然后取平均值。使用IO流的文件传输似乎是最糟糕的候选者:

方法如下:

private static long fileCopyUsingFileStreams(File fileToCopy, File newFile) throws IOException {
    FileInputStream input = new FileInputStream(fileToCopy);
    FileOutputStream output = new FileOutputStream(newFile);
    byte[] buf = new byte[1024];
    int bytesRead;
    long start = System.currentTimeMillis();
    while ((bytesRead = input.read(buf)) > 0)
    {
        output.write(buf, 0, bytesRead);
    }
    long end = System.currentTimeMillis();

    input.close();
    output.close();

    return (end-start);
}

private static long fileCopyUsingNIOChannelClass(File fileToCopy, File newFile) throws IOException
{
    FileInputStream inputStream = new FileInputStream(fileToCopy);
    FileChannel inChannel = inputStream.getChannel();

    FileOutputStream outputStream = new FileOutputStream(newFile);
    FileChannel outChannel = outputStream.getChannel();

    long start = System.currentTimeMillis();
    inChannel.transferTo(0, fileToCopy.length(), outChannel);
    long end = System.currentTimeMillis();

    inputStream.close();
    outputStream.close();

    return (end-start);
}

private static long fileCopyUsingApacheCommons(File fileToCopy, File newFile) throws IOException
{
    long start = System.currentTimeMillis();
    FileUtils.copyFile(fileToCopy, newFile);
    long end = System.currentTimeMillis();
    return (end-start);
}

private static long fileCopyUsingNIOFilesClass(File fileToCopy, File newFile) throws IOException
{
    Path source = Paths.get(fileToCopy.getPath());
    Path destination = Paths.get(newFile.getPath());
    long start = System.currentTimeMillis();
    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
    long end = System.currentTimeMillis();

    return (end-start);
}

我在使用NIO通道类时看到的唯一缺点是,我似乎仍然无法找到一种方法来显示中间文件复制进度。

根据我的测试,带缓冲区的NIO复制是最快的。下面的工作代码来自我在https://github.com/mhisoft/fastcopy上的一个测试项目

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;


public class test {

private static final int BUFFER = 4096*16;
static final DecimalFormat df = new DecimalFormat("#,###.##");
public static void nioBufferCopy(final File source, final File target )  {
    FileChannel in = null;
    FileChannel out = null;
    double  size=0;
    long overallT1 =  System.currentTimeMillis();

    try {
        in = new FileInputStream(source).getChannel();
        out = new FileOutputStream(target).getChannel();
        size = in.size();
        double size2InKB = size / 1024 ;
        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER);

        while (in.read(buffer) != -1) {
            buffer.flip();

            while(buffer.hasRemaining()){
                out.write(buffer);
            }

            buffer.clear();
        }
        long overallT2 =  System.currentTimeMillis();
        System.out.println(String.format("Copied %s KB in %s millisecs", df.format(size2InKB),  (overallT2 - overallT1)));
    }
    catch (IOException e) {
        e.printStackTrace();
    }

    finally {
        close(in);
        close(out);
    }
}

private static void close(Closeable closable)  {
    if (closable != null) {
        try {
            closable.close();
        } catch (IOException e) {
            if (FastCopy.debug)
                e.printStackTrace();
        }    
    }
}

}