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


421

一直困扰着我,用Java复制文件的唯一方法是打开流,声明缓冲区,读入一个文件,循环遍历并将其写出到另一流。Web上散布着这种解决方案的类似但仍然略有不同的实现。

有没有一种更好的方法可以保留在Java语言的范围内(意味着不涉及执行OS特定的命令)?也许在某个可靠的开源实用程序包中,这至少会掩盖该基本实现并提供一线解决方案?


5
有可能是一些在Apache中共享文件实用程序,具体地讲,的CopyFile方法。
工具包

22
如果使用Java 7,请按照@GlenBest的建议使用Files.copy代替:stackoverflow.com/a/16600787/44737
rob

Answers:


274

如上面的工具包所述,Apache Commons IO尤其是FileUtils是必经之路。copyFile() ; 它为您处理所有繁重的工作。

并且作为一个后记,请注意,FileUtils的最新版本(例如2.0.1发行版)增加了使用NIO复制文件的功能。NIO可以大大提高文件复制性能,这在很大程度上是因为NIO例程将复制直接推迟到OS /文件系统,而不是通过在Java层中读写字节来处理。因此,如果您正在寻找性能,那么可能值得检查一下您是否在使用最新版本的FileUtils。


1
非常有帮助-您对正式版本何时将包含这些nio更改有任何见解吗?
彼得

2
Apache Commons IO的公共发行版仍为1.4,版本为grrrrrrr
Peter

14
截至2010年12月,Apache Commons IO的版本为2.0.1,具有NIO功能。答案已更新。
西蒙·尼克森

4
对Android用户的警告:标准Android API中未包含此内容
IlDan 2012年

18
如果使用Java 7或更高版本,则可以按照@GlenBest的建议使用Files.copy:stackoverflow.com/a/16600787/44737
rob

278

我会避免使用像Apache Commons这样的大型api。这是一个简单的操作,它内置在新NIO包中的JDK中。以前的答案中已经链接了这种方法,但是NIO api中的关键方法是新功能“ transferTo”和“ transferFrom”。

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html#transferTo(long,%20long,%20java.nio.channels.WritableByteChannel)

链接的文章之一展示了一种很好的方法,可以使用transferFrom将功能集成到代码中:

public static void copyFile(File sourceFile, File destFile) throws IOException {
    if(!destFile.exists()) {
        destFile.createNewFile();
    }

    FileChannel source = null;
    FileChannel destination = null;

    try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destFile).getChannel();
        destination.transferFrom(source, 0, source.size());
    }
    finally {
        if(source != null) {
            source.close();
        }
        if(destination != null) {
            destination.close();
        }
    }
}

学习NIO可能会有些棘手,因此您可能只想在开始尝试通宵学习NIO之前就信任此机制。从个人经验来看,如果您没有经验,并且是通过java.io流介绍给IO的,则可能很难学习。


2
谢谢,有用的信息。我仍然会为Apache Commons之类的产品辩护,尤其是如果它在下面使用nio(正确)的话;但我同意了解基本原理很重要。
彼得

1
不幸的是,有一些警告。当我在Windows 7(32位)上复制1.5 Gb文件时,无法映射该文件。我不得不寻找另一种解决方案。
安东·K,

15
上面的代码可能引起三个问题:(a)如果getChannel引发异常,则可能会泄漏开放流;(b)对于大文件,您可能试图一次传输超出操作系统处理能力的文件;(c)您忽略了transferFrom的返回值,因此它可能仅复制文件的一部分。这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂的原因。还要注意,尽管transferFrom正常,但在Linux上的JDK 1.4上transferTo中断:bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395
Jesse Glick

7
我认为此更新版本解决了这些问题:gist.github.com/889747
Mark Renouf

11
此代码有一个主要问题。transferTo()必须在循环中调用。不保证会转账所有要求的金额。
2013年

180

现在,在Java 7中,您可以使用以下try-with-resource语法:

public static void copyFile( File from, File to ) throws IOException {

    if ( !to.exists() ) { to.createNewFile(); }

    try (
        FileChannel in = new FileInputStream( from ).getChannel();
        FileChannel out = new FileOutputStream( to ).getChannel() ) {

        out.transferFrom( in, 0, in.size() );
    }
}

或者,更好的是,这也可以使用Java 7中引入的新Files类来实现:

public static void copyFile( File from, File to ) throws IOException {
    Files.copy( from.toPath(), to.toPath() );
}

很时髦,是吗?


15
令人惊讶的是Java在今天之前还没有添加这样的东西。某些操作只是编写计算机软件的绝对必要条件。Java的Oracle开发人员可以从操作系统中学到一两个东西,查看他们提供的服务,从而使新手更容易迁移。
里克·霍金

2
啊,谢谢!我不知道带有所有辅助功能的新“文件”类。它正是我需要的。谢谢你的例子。
ChrisCantrell 2012年

1
性能方面,Java NIO FileChannel更好,请阅读此文章journaldev.com/861/4-way-to-copy-file-in-java
Pankaj

5
此代码有一个主要问题。transferTo()必须在循环中调用。不保证会转账所有要求的金额。
2013年

@斯科特:皮特问一个单行解决方案,你是如此接近...没有必要将copyfile.copy包装在copyFile方法中。我只是将Files.copy(Path from,Path to)放在答案的开头,并提到如果您已有现有File对象,则可以使用File.toPath():Files.copy(fromFile.toPath(), toFile.toPath())
抢劫

89
  • 这些方法是性能工程设计的(它们与操作系统本机I / O集成在一起)。
  • 这些方法适用于文件,目录和链接。
  • 提供的每个选项都可以省略-它们是可选的。

实用类

package com.yourcompany.nio;

class Files {

    static int copyRecursive(Path source, Path target, boolean prompt, CopyOptions options...) {
        CopyVisitor copyVisitor = new CopyVisitor(source, target, options).copy();
        EnumSet<FileVisitOption> fileVisitOpts;
        if (Arrays.toList(options).contains(java.nio.file.LinkOption.NOFOLLOW_LINKS) {
            fileVisitOpts = EnumSet.noneOf(FileVisitOption.class) 
        } else {
            fileVisitOpts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        }
        Files.walkFileTree(source[i], fileVisitOpts, Integer.MAX_VALUE, copyVisitor);
    }

    private class CopyVisitor implements FileVisitor<Path>  {
        final Path source;
        final Path target;
        final CopyOptions[] options;

        CopyVisitor(Path source, Path target, CopyOptions options...) {
             this.source = source;  this.target = target;  this.options = options;
        };

        @Override
        FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        // before visiting entries in a directory we copy the directory
        // (okay if directory already exists).
        Path newdir = target.resolve(source.relativize(dir));
        try {
            Files.copy(dir, newdir, options);
        } catch (FileAlreadyExistsException x) {
            // ignore
        } catch (IOException x) {
            System.err.format("Unable to create: %s: %s%n", newdir, x);
            return SKIP_SUBTREE;
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        Path newfile= target.resolve(source.relativize(file));
        try {
            Files.copy(file, newfile, options);
        } catch (IOException x) {
            System.err.format("Unable to copy: %s: %s%n", source, x);
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        // fix up modification time of directory when done
        if (exc == null && Arrays.toList(options).contains(COPY_ATTRIBUTES)) {
            Path newdir = target.resolve(source.relativize(dir));
            try {
                FileTime time = Files.getLastModifiedTime(dir);
                Files.setLastModifiedTime(newdir, time);
            } catch (IOException x) {
                System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
            }
        }
        return CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        if (exc instanceof FileSystemLoopException) {
            System.err.println("cycle detected: " + file);
        } else {
            System.err.format("Unable to copy: %s: %s%n", file, exc);
        }
        return CONTINUE;
    }
}

复制目录或文件

long bytes = java.nio.file.Files.copy( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES,
                 java.nio.file.LinkOption.NOFOLLOW_LINKS);

移动目录或文件

long bytes = java.nio.file.Files.move( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.ATOMIC_MOVE,
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING);

递归复制目录或文件

long bytes = com.yourcompany.nio.Files.copyRecursive( 
                 new java.io.File("<filepath1>").toPath(), 
                 new java.io.File("<filepath2>").toPath(),
                 java.nio.file.StandardCopyOption.REPLACE_EXISTING,
                 java.nio.file.StandardCopyOption.COPY_ATTRIBUTES
                 java.nio.file.LinkOption.NOFOLLOW_LINKS );

Files的软件包名称错误(应该是java.nio.file而不是java.nio)。我已经为此提交了编辑;希望没关系!
Stuart Rossiter 2014年

43

在Java 7中很容易...

File src = new File("original.txt");
File target = new File("copy.txt");

Files.copy(src.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

1
您的答案对斯科特或格伦有什么帮助?
Uri Agassi 2014年

11
简洁明了,少即是多。他们的回答很好而且很详尽,但是当我翻阅时,我想念了他们。不幸的是,对此有很多答案,其中很多都是漫长,过时和复杂的,而斯科特和格伦的好答案就此丢掉了(我会投票赞成)。我想知道是否可以通过剔除exist()和错误消息而将答案减少到三行来改善答案。
凯文·萨德勒

这不适用于目录。该死的每个人都错了。更多的API通信会引发您的错误。我也弄错了。
毫米

2
@momo的问题是如何复制文件。
凯文·萨德勒

28

要复制文件并将其保存到目标路径,可以使用以下方法。

public void copy(File src, File dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

1
这会起作用,但是我认为这比这里的其他答案更好吗?
Rup

2
@Rup它比这里的其他答案要好得多,(a)因为它有效,(b)因为它不依赖第三方软件。
罗恩侯爵

1
@EJP可以,但是不是很聪明。文件复制应该是OS或文件系统操作,而不是应用程序操作:希望Java能够发现副本并将其转变为OS操作,除非在停止文件操作时明确读取文件。如果您认为Java无法做到这一点,您是否会相信它可以优化对较大块的1K读写操作?而且,如果源和目标位于通过慢速网络的远程共享上,那么这显然是在做不必要的工作。是的,有些第三方JAR很大(Guava!),但是它们确实添加了很多类似的东西。
Rup

像魅力一样工作。不需要第三方库且可在Java 1.6上运行的最佳解决方案。谢谢。
James Wierzba

@Rup我同意这应该是操作系统功能,但是我对您的评论没有其他任何意义。第一个冒号之后的部分缺少动词;尽管我当然会自己使用更大的块,但我也不会“信任”指望Java将1k块变成更大的块。我永远不会写一个首先使用共享文件的应用程序。而且我不知道任何第三方库除了使用更大的缓冲区外,没有比此代码更“适当”的功能(无论您指的是什么)。
罗恩侯爵

24

请注意,所有这些机制仅复制文件的内容,而不复制诸如权限之类的元数据。因此,如果您要在Linux上复制或移动可执行文件.sh文件,则新文件将不可执行。

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

显然,这可以在Java 7- http://today.java.net/pub/a/today/2008/07/03/jsr-203-new-file-apis.html中修复。手指交叉!


23

Google的Guava库也有一个复制方法

公共静态无效副本文件  从,
                         文件  到)
                 引发IOException
将所有字节从一个文件复制到另一个文件。

警告:如果to代表现有文件,则该文件将被的内容覆盖from。如果to并且 from引用相同的文件,则该文件的内容将被删除。

参数:from -源文件to-目标文件

抛出: IOException -如果发生I / O错误 IllegalArgumentException-如果from.equals(to)



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



7

您可以通过以下三种方式轻松地用单行代码复制文件!

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

番石榴

Files#copy

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

第一个不适用于目录。该死的每个人都错了。更多的API通信会引发您的错误。我也弄错了。
毫米

第一个需要3个参数。Files.copy仅使用2个参数Path即可Stream。只需添加参数StandardCopyOption.COPY_ATTRIBUTESStandardCopyOption.REPLACE_EXISTINGPathPath
皮条客Trizkit

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()调用是多余且浪费的。
罗恩侯爵

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流..仅在160秒内复制10GB
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

不适用于1.4之前的任何Java版本,并且没有任何东西可以保证一次写入就足够了。
罗恩侯爵

1

晚会晚了一点,但这是使用各种文件复制方法复制文件所花费时间的比较。我遍历了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通道类时看到的唯一缺点是,我似乎仍然找不到找到中间文件复制进度的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.