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

430

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

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


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

280

我会避免使用像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(正确地);但我同意了解基本原理的重要性。 - Peter
1
不幸的是,还存在一些注意事项。当我在32位Windows 7上复制1.5 Gb文件时,它无法映射该文件。我不得不寻找另一个解决方案。 - Anton K.
16
上述代码可能存在以下三个问题:(a) 如果getChannel抛出异常,可能会泄漏一个打开的流; (b) 对于大文件,您可能会尝试一次性传输超过操作系统处理能力的数据量; (c) 您忽略了transferFrom方法的返回值,因此它可能只复制文件的一部分。这就是为什么org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂的原因。同时请注意,虽然transferFrom方法是可行的,但在Linux的JDK 1.4版本中,transferTo方法会出现问题:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5056395 - Jesse Glick
7
我相信这个更新版本解决了那些问题:https://gist.github.com/889747 - Mark Renouf
11
这段代码有一个严重的问题,必须在循环中调用transferTo()。它不能保证转移完整请求的金额。 - user207421
显示剩余5条评论

277

如工具包中所述,Apache Commons IO 是可行的解决方案,尤其是要使用FileUtilscopyFile()方法将为您处理所有繁重的工作。

另外,需要注意的是,最近版本的FileUtils(例如2.0.1版本)已经添加了NIO来复制文件。 NIO 可以显著提高文件复制性能, 这在很大程度上是因为NIO例程通过将直接将复制推迟到操作系统/文件系统处理,而不是通过Java层读取和写入字节来处理。如果您正在寻找性能,则值得检查是否使用最新版本的FileUtils。


1
非常有帮助 - 你是否知道官方发布何时会包含这些nio更改的见解? - Peter
2
Apache Commons IO 的公共版本仍停留在1.4,真是气人。 - Peter
14
截至2010年12月,Apache Commons IO的版本为2.0.1,其中包含NIO功能。回答已更新。 - Simon Nickerson
4
提醒Android用户:此内容不包含在标准Android API中。 - IlDan
18
如果使用Java 7或更高版本,您可以像@GlenBest建议的那样使用Files.copy:http://www.stackoverflow.com/a/16600787/44737 - rob
显示剩余2条评论

183

现在在Java 7中,你可以使用以下带资源的try语法:

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开发人员可以向操作系统学习,看看它们提供了哪些服务,以使新手更容易迁移过来。 - Rick Hodgin
2
啊,谢谢!我之前不知道有新的“文件”类及其所有帮助函数。这正好是我所需要的。感谢您提供的示例。 - ChrisCantrell
1
就性能而言,Java NIO FileChannel 更好,阅读此文章 http://www.journaldev.com/861/4-ways-to-copy-file-in-java - Pankaj
5
这段代码存在一个严重的问题。必须在循环中调用transferTo()方法,它不能保证转移完整请求的金额。 - user207421
@Scott:Pete要求一行解决方案,而你已经非常接近了……将Files.copy包装在copyFile方法中是不必要的。我只会把Files.copy(Path from, Path to)放在你的答案开头,并提到如果你有现有的File对象,可以使用File.toPath():Files.copy(fromFile.toPath(), toFile.toPath())。 - rob

94
  • 这些方法是性能优化的(它们与操作系统的本地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 );

文件的包名称是错误的(应该是 java.nio.file 而不是 java.nio)。我已经提交了一个编辑;希望这没问题! - Stuart Rossiter
1
在使用Paths.get("<filepath1>")的情况下,写new java.io.File("<filepath1>").toPath()没有任何意义。 - Holger

52

在Java 7中,这很容易...

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

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

1
你的回答对Scott或Glen的回答有什么补充? - Uri Agassi
14
简明扼要,少即是多。他们的回答很好而且详细,但我在浏览时错过了它们。不幸的是,这里有很多答案,其中许多答案既长又过时,复杂难懂,而Scott和Glen的好答案却因此被忽视了(我会点赞来帮助解决这个问题)。我想知道如果删去exists()和错误消息,将我的答案简化为三行是否会更好。 - Kevin Sadler
这对目录无效。该死,每个人都弄错了这个。更像是API通信问题,是你的错。我也搞错了。 - mjs
4
如何复制文件。 - Kevin Sadler
2
当您需要一个路径时,无需走“文件”绕路。Files.copy(Paths.get("original.txt"), Paths.get("copy.txt"), ...) - Holger

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) 因为它不依赖于第三方软件。 - user207421
1
@EJP 好的,但这并不是很聪明。文件复制应该是操作系统或文件系统操作,而不是应用程序操作:Java希望能够检测到复制并将其转换为操作系统操作,除非您显式地读取文件,否则它无法执行此操作。如果您认为Java不能做到这一点,那么您会相信它将1K读写优化为更大的块吗?如果源和目标位于慢速网络上的远程共享中,则这显然是在做不必要的工作。是的,有些第三方JAR包非常庞大(Guava!),但如果正确完成,它们确实添加了很多类似的功能。 - Rup
像魔法般好用。最佳解决方案,不需要第三方库,并且适用于Java 1.6。谢谢。 - James Wierzba
@Rup 我同意这应该是操作系统的功能,但我无法理解你评论后面的部分。第一个冒号后面缺少一个动词;我既不会“信任”也不会期望Java将1k块转换为更大的东西,尽管我自己肯定会使用更大的块;我从来不会编写首先使用共享文件的应用程序;我不知道任何第三方库比这段代码做得更好(无论你所说的是什么),除了可能使用更大的缓冲区。 - user207421
显示剩余2条评论

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库也有一个复制方法:

public static void copy(File from,
                        File to)
                 throws IOException
将一个文件的所有字节复制到另一个文件。

注意: 如果to代表一个已存在的文件,那么该文件将被覆盖为from的内容。如果tofrom指向同一文件,则该文件的内容将被删除。

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

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


18

10
没有Path.copyTo方法,应使用Files.copy方法。 - Jesse Glick

7

如果你正在使用Spring框架的Web应用程序,而且不想为简单的文件复制添加Apache Commons IO,那么你可以使用Spring框架的FileCopyUtils


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