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

Posted

技术标签:

【中文标题】用Java复制文件的标准简洁方法?【英文标题】:Standard concise way to copy a file in Java? 【发布时间】:2010-09-11 12:42:15 【问题描述】:

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

有没有更好的方法来保持在 Java 语言的范围内(意味着不涉及执行操作系统特定的命令)?也许在一些可靠的开源实用程序包中,这至少会掩盖这个底层实现并提供一个单一的解决方案?

【问题讨论】:

Apache Commons FileUtils 中可能有一些东西,特别是 copyFile 方法。 如果使用 Java 7,请改用 Files.copy,正如 @GlenBest 推荐的那样:***.com/a/16600787/44737 【参考方案1】:

我会避免使用像 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,那么学习可能会非常困难。

【讨论】:

谢谢,有用的信息。我仍然会支持像 Apache Commons 这样的东西,特别是如果它在下面使用 nio(正确);但我同意了解基本原理很重要。 不幸的是,有一些警告。当我在 32 位 Windows 7 上复制 1.5 Gb 文件时,它无法映射文件。我不得不寻找另一种解决方案。 上述代码可能存在的三个问题: (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 我相信这个更新版本解决了这些问题:gist.github.com/889747 这段代码有一个主要问题。 transferTo() 必须在循环中调用。它不能保证转移请求的全部金额。【参考方案2】:

正如上面提到的工具包,Apache Commons IO 是要走的路,特别是FileUtils.copyFile();它为您处理所有繁重的工作。

作为后记,请注意最近的 FileUtils 版本(例如 2.0.1 版本)添加了使用 NIO 来复制文件; NIO can significantly increase file-copying performance,很大程度上是因为 NIO 例程推迟直接复制到操作系统/文件系统,而不是通过 Java 层读取和写入字节来处理它。因此,如果您正在寻找性能,可能值得检查一下您使用的是最新版本的 FileUtils。

【讨论】:

非常有帮助 - 您知道官方版本何时会包含这些 nio 更改吗? Apache Commons IO 的公开版本仍为 1.4,grrrrrrr 截至 2010 年 12 月,Apache Commons IO 为 2.0.1,具有 NIO 功能。答案已更新。 android 用户的警告:这不包含在标准 Android API 中 如果使用 Java 7 或更新版本,您可以按照 @GlenBest 的建议使用 Files.copy:***.com/a/16600787/44737【参考方案3】:

现在使用 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() );

很时髦,嗯?

【讨论】:

令人惊奇的是,Java 在今天之前还没有添加这样的东西。某些操作只是编写计算机软件的绝对要领。 Java 的 Oracle 开发人员可以从操作系统中学习一两件事,查看他们提供的服务,以使新手更容易迁移。 啊,谢谢!我不知道带有所有辅助函数的新“文件”类。它正是我需要的。谢谢你的例子。 性能方面,java NIO FileChannel 更好,阅读这篇文章journaldev.com/861/4-ways-to-copy-file-in-java 这段代码有一个主要问题。 transferTo() 必须在循环中调用。它不能保证转移请求的全部金额。 @Scott:Pete 要求提供单行解决方案,而您已经很接近了……没有必要将 Files.copy 包装在 copyFile 方法中。我只是将 Files.copy(Path from, Path to) 放在答案的开头,并提到如果您有现有的 File 对象,您可以使用 File.toPath(): Files.copy(fromFile.toPath(), toFile.toPath())【参考方案4】: 这些方法是针对性能设计的(它们与操作系统原生 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)。我已经为此提交了编辑;希望没关系! 当您可以使用Paths.get("&lt;filepath1&gt;") 时,写new java.io.File("&lt;filepath1&gt;").toPath() 毫无意义。【参考方案5】:

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

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

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

【讨论】:

你的回答对 Scott 或 Glen 有什么影响? 简洁,少即是多。他们的答案很好而且很详细,但是我在看的时候错过了他们。不幸的是,对此有很多答案,其中很多都是冗长、过时和复杂的,斯科特和格伦的好答案在其中丢失了(我会放弃投票来帮助解决这个问题)。我想知道是否可以通过删除 exists() 和错误消息将其减少到三行来改进我的答案。 这不适用于目录。该死的,每个人都把这个弄错了。更多的 API 通信问题是你的错。我也弄错了。 @momo 问题是如何复制文件。 当您需要Path 时,无需绕道FileFiles.copy(Paths.get("original.txt"), Paths.get("copy.txt"), …)【参考方案6】:

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

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

【讨论】:

这行得通,但我不认为它比这里的其他答案更好? @Rup 它比这里的其他答案要好得多,(a)因为它有效,(b)因为它不依赖第三方软件。 @EJP 好的,但它不是很聪明。文件复制应该是操作系统或文件系统操作,而不是应用程序操作:Java 希望能够发现副本并将其转换为操作系统操作,除非通过显式读取文件来阻止它这样做。如果您不认为 Java 可以做到这一点,您会相信它可以将 1K 读写优化到更大的块中吗?如果源和目标位于慢速网络上的远程共享上,那么这显然是在做不必要的工作。是的,一些第三方 JAR 非常大(Guava!),但它们确实添加了很多这样的东西。 工作就像一个魅力。不需要 3rd 方库并适用于 java 1.6 的最佳解决方案。谢谢。 @Rup 我同意它应该是一个操作系统功能,但我无法理解您的评论。第一个冒号之后的部分在某处缺少动词;我既不会“相信”也不会期望 Java 将 1k 块变成更大的块,尽管我自己肯定会使用更大的块;我永远不会写一个使用共享文件的应用程序。而且我不知道任何第三方库比这段代码更“正确”(不管你的意思是什么),除了可能使用更大的缓冲区。【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

Google 的 Guava 库也有一个copy method:

public static void copy(File 来自, File 至) 抛出IOException
将所有字节从一个文件复制到另一个文件。

警告:如果to 表示现有文件,则该文件 将被from 的内容覆盖。如果tofrom 指的是 same 文件,那个文件的内容 将被删除。

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

抛出: @987654325@ - 如果发生 I/O 错误 @987654326@ - 如果from.equals(to)

【讨论】:

【参考方案9】:

在 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

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

【讨论】:

没有Path.copyTo;它是 Files.copy。【参考方案10】:

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

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

这就是org.apache.tools.ant.util.ResourceUtils.copyResource 如此复杂的原因。另请注意,虽然 transferFrom 可以,但在 Linux 上的 JDK 1.4 上 transferTo 会中断(请参阅 Bug ID:5056395)– Jesse Glick Jan

【讨论】:

【参考方案11】:

如果您在一个已经使用 Spring 的 Web 应用程序中,并且不想包含 Apache Commons IO 来进行简单的文件复制,则可以使用 Spring 框架的FileCopyUtils。

【讨论】:

【参考方案12】:
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? 甚至都不编译,createNewFile() 调用是多余且浪费的。【参考方案13】:

这三种方法可以让你轻松地用一行代码复制文件!

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 个参数用于 PathStream。只需将Path 的参数StandardCopyOption.COPY_ATTRIBUTESStandardCopyOption.REPLACE_EXISTING 添加到Path【参考方案14】:

根据我的测试,带有缓冲区的 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【参考方案15】:

快速且适用于所有版本的 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.4 之前的任何 Java 版本,也无法保证一次写入就足够了。【参考方案16】:

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

【讨论】:

以上是关于用Java复制文件的标准简洁方法?的主要内容,如果未能解决你的问题,请参考以下文章

xshell用啥上传文件

用java控制流实现文件复制后,不能删除文件

使用 Java 8 中的功能,转换列表的所有值的最简洁方法是啥? [复制]

java IO将一个文件复制到另一个

Java 8:递归复制目录?

Java 实现文件复制的不同方法