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

Standard concise way to copy a file in Java?

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

是否有更好的方式保持在Java语言的范围内(意思是不涉及执行特定于OS的命令)?也许在一些可靠的开源实用程序包中,这至少会掩盖底层实现并提供一个单行解决方案?


我将避免使用像ApacheCommons这样的大型API。这是一个简单的操作,它内置于新NIO包中的JDK中。它在以前的答案中已经有了链接,但是NIO API中的关键方法是新的函数"transferto"和"transfertfrom"。

http://java.xun.com /javas/6 /DOCS/API/Java/NiO/通道/FielChan.html,HTML传输(long,%20long,%20java. Nio.hans.Wrrable ByThanChank)

其中一篇链接文章展示了如何使用TransferFrom将此函数集成到代码中的一个很好的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,那么学习它可能是一件非常困难的事情。


正如工具包上面提到的,ApacheCommonsIO是一种方法,特别是fileutils.copyFile();它为您处理所有繁重的工作。

作为一个后记,注意最近版本的FieluTyl(比如2.0.1版本)已经添加了用于复制文件的NIO;NIO可以显著地提高文件复制性能,这很大程度上是因为NIO例程推迟直接拷贝到OS /文件系统,而不是通过Java层读取和写入字节来处理它。因此,如果您在寻找性能,可能需要检查一下您是否使用了最新版本的fileutils。


现在使用Java 7,可以使用下面的资源语法尝试:

1
2
3
4
5
6
7
8
9
10
11
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中引入的新文件类来完成:

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

很时髦,是吗?


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

实用程序类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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;
    }
}

复制目录或文件

1
2
3
4
5
6
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);

移动目录或文件

1
2
3
4
5
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);

递归复制目录或文件

1
2
3
4
5
6
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 7中很容易…

1
2
3
4
File src = new File("original.txt");
File target = new File("copy.txt");

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
    }
}


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

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

显然,这可能是固定在Java 7 - HTTP://toay.java. NET/PUB/A/ToDay/A88/07/03/JSR-203-NeX-FLIP-APIS.HTML中。交叉手指!


谷歌的番石榴图书馆也有一个复制方法:

< PRE>公共静态void copy(a href="http://download.oracle.com/javase/6/docs/api/java/io/file.html?"外部=真"title ="类或接口在java. IO >文件{/a}},a href="http://download.oracle.com/javase/6/docs/api/java/io/file.html?"外部=真"title ="类或接口在java. IO >文件{a/a}和nbsp to抛出a href="http://download.oracle.com/javase/6/docs/api/java/io/ioexception.html?"外部=真"title ="类或接口在java. IO"> IOExab{{AA}< DL>

将所有字节从一个文件复制到另一个文件。

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

< DL>

参数:from源文件to目的文件

投掷:a href="http://download.oracle.com/javase/6/docs/api/java/io/ioexception.html?"如果发生I/O错误,则外部=真"title ="类或接口在java. IO"> IOExtExe/AA} < /代码>a href="http://download.oracle.com/javase/6/docs/api/java/lang/illegalargumentexception.html?"外部=真"title ="类或接口在java.郎> iLalalAgMuMutExtExt[A/A} < /代码> -如果EDCOX1>8</dL>


在Java 7中可用作为标准,PAT.COMPTO:http://openjdk.java.net/projects/nio/javadoc/java/nio/file/path.htmlhttp://java.sun.com/docs/books/tutorial/essential/io/copy.html网站

我真不敢相信他们花了这么长时间才将文件复制这样普通而简单的东西标准化:(


如果您在一个已经使用Spring的Web应用程序中,并且不希望包含用于简单文件复制的ApacheCommonsIO,那么可以使用Spring框架的fileCopyUtils。


这里有三种方法,您可以轻松地用一行代码复制文件!

Java7:

Java.Nio.file文件拷贝

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

阿帕奇公地IO:

fileutils复制文件

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

番石榴:

文件拷贝

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


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

  • 如果getchannel抛出异常,则可能会泄漏开放流。
  • 对于大型文件,您可能试图一次传输的次数超过操作系统所能处理的次数。
  • 您忽略了transferFrom的返回值,因此它可能只是复制文件的一部分。
  • 这就是org.apache.tools.ant.util.ResourceUtils.copyResource如此复杂的原因。另外请注意,当transferFrom正常时,transferTo在Linux上的JDK 1.4中断(请参见bug id:5056395)–jesse glick jan


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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();
            }
        }
    }


    根据我的测试,使用缓冲区的NIO拷贝是最快的。我的测试项目见以下工作代码,网址为https://github.com/mhift/fastcopy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    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也安卓:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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();
    }


    派对稍微晚了一点,但这里比较了使用各种文件复制方法复制文件所花费的时间。我反复检查了10次方法,并取了平均值。使用IO流的文件传输似乎是最坏的选择:

    Comparison of file transfer using various methods

    方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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通道类时看到的唯一缺点是,我仍然找不到显示中间文件复制进度的方法。