在Java中标准简洁的复制文件方法是什么?

430

在Java中,想要复制文件的唯一方式似乎总是需要打开流、声明缓冲区、读取一个文件并对其进行循环处理,最后将其写入另一个流中。网络上有很多类似的解决方案,但都有些微不同。

是否有更好的方法可以在Java语言范围内实现(也就是不涉及执行特定于操作系统的命令)?也许有可靠的开源工具包可以提供一行代码的解决方案来隐藏底层实现细节?


5
Apache Commons中可能有一些内容与copyFile方法有关,具体来说是FileUtils - toolkit
24
如果使用Java 7,请使用Files.copy,正如@GlenBest所建议的那样:https://dev59.com/8nVD5IYBdhLWcg3wDHDm#16600787 - rob
16个回答

7

以上代码可能存在三个问题:

  1. 如果getChannel引发异常,你可能会泄露一个打开的流。
  2. 对于大文件,你可能一次性尝试传输超过操作系统处理能力的数据量。
  3. 你忽略了transferFrom的返回值,因此它可能只复制了文件的一部分。

这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂。同时请注意,虽然transferFrom是可以的,但transferTo在Linux的JDK 1.4上会出现问题(参见Bug ID:5056395)- Jesse Glick Jan


6
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();
        }
    }
}

那么与顶部被接受的答案不同之处在于,您在while循环中使用了transferFrom吗? - Rup
1
甚至无法编译,而createNewFile()调用是多余和浪费的。 - user207421

6

以下是三种只需一行代码即可轻松复制文件的方法!

Java7:

java.nio.file.Files#copy

private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

Appache Commons IO:

FileUtils#copyFile

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

Guava

Files#copy 方法:

private static void copyFileUsingGuava(File source,File dest) throws IOException{
    Files.copy(source,dest);          
}

第一个无法用于目录。该死,每个人都弄错了这个。更多是API通信问题,是你的错。我也弄错了。 - mjs
首先需要3个参数。仅使用2个参数的Files.copy是用于从PathStream的。只需为从PathPath添加参数StandardCopyOption.COPY_ATTRIBUTESStandardCopyOption.REPLACE_EXISTING即可。 - Pimp Trizkit

3

根据我的测试,使用缓冲区的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();
        }    
    }
}

}


不错!这个比标准的java.io流快得多.. 复制10GB只需要160秒 - aswzen

2

快速并且可以与所有版本的Java和Android一起使用:

private void copy(final File f1, final File f2) throws IOException {
    f2.createNewFile();

    final RandomAccessFile file1 = new RandomAccessFile(f1, "r");
    final RandomAccessFile file2 = new RandomAccessFile(f2, "rw");

    file2.getChannel().write(file1.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f1.length()));

    file1.close();
    file2.close();
}

1
并非所有的文件系统都支持内存映射文件,而且我认为对于小文件来说这相对较昂贵。 - Rup
不支持Java 1.4之前的任何版本,并且没有任何保证单个写操作足够。 - user207421

1

虽然有点晚了,但这里是使用不同文件复制方法复制文件所需时间的比较。我循环运行了这些方法10次,并取得了平均值。使用IO流进行文件传输似乎是最差的选择:

Comparison of file transfer using various methods

这里是方法列表:
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通道类唯一的缺点是,我仍然找不到显示中间文件复制进度的方法。

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