Java 8:如何递归地复制目录?

44

我看到Java 8已经显著简化了将文件内容读入字符串的过程:

String contents = new String(Files.readAllBytes(Paths.get(new URI(someUrl))));

我想知道是否有类似的东西(更简洁/代码更少/更简练)可以递归复制目录。在 Java 7 中,它仍然是这样的:

public void copyFolder(File src, File dest) throws IOException{
    if(src.isDirectory()){
        if(!dest.exists()){
            dest.mkdir();
        }

        String files[] = src.list();

        for (String file : files) {
            File srcFile = new File(src, file);
            File destFile = new File(dest, file);

            copyFolder(srcFile,destFile);
        }

    } else {
        InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dest); 

        byte[] buffer = new byte[1024];

        int length;
        while ((length = in.read(buffer)) > 0){
            out.write(buffer, 0, length);
        }

        in.close();
        out.close();
    }
}

Java 8有哪些改进?


2
FYI Files.readAllBytes(Paths.get(new URI(someUrl)) 自Java 7起可用。 - assylias
1
else块可以使用java.nio.file.Files#copy(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption...)完成,该方法自1.7版本起可用。 - bowmore
3
请查看 FileVisitor 的 Javadoc 中的示例:http://docs.oracle.com/javase/7/docs/api/java/nio/file/FileVisitor.html - assylias
3
这个org.apache.commons.io.FileUtils.copyDirectory(File, File)怎么样?它可以安全地完成工作... :D - udoline
7个回答

32

这样,代码看起来会简单一些。

import static java.nio.file.StandardCopyOption.*;

public  void copyFolder(Path src, Path dest) throws IOException {
    try (Stream<Path> stream = Files.walk(src)) {
        stream.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
    }
}

private void copy(Path source, Path dest) {
    try {
        Files.copy(source, dest, REPLACE_EXISTING);
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

2
它会复制子文件夹及其内容。 - Romano
1
-1:流永远不会关闭。https://dev59.com/gV4b5IYBdhLWcg3wdxnG#60621544 更好。 - Olivier Cailloux
1
你在说哪个流?这个解决方案没有使用流。 - Christian Schneider
1
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#walk(java.nio.file.Path,java.nio.file.FileVisitOption...) - Olivier Cailloux
1
我删掉了-1(但是我仍然更喜欢这个答案,因为那里列出的原因)。 - Olivier Cailloux
显示剩余3条评论

27

使用 Files.walkFileTree

  • 您不需要担心关闭流。
    (有些其他答案在使用 Files.walk 时会忘记这一点)
  • 它可以优雅地处理 IOException
    (某些其他答案在添加适当的异常处理而不是简单的 printStackTrace 时会变得更加困难)
    public void copyFolder(Path source, Path target, CopyOption... options)
            throws IOException {
        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                    throws IOException {
                Files.createDirectories(target.resolve(source.relativize(dir).toString()));
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                    throws IOException {
                Files.copy(file, target.resolve(source.relativize(file).toString()), options);
                return FileVisitResult.CONTINUE;
            }
        });
    }

这段代码的作用是

  • 递归遍历目录中的所有文件。
  • 当遇到一个目录时 (preVisitDirectory):
    在目标目录中创建相应的目录。
  • 当遇到一个普通文件时 (visitFile):
    复制它。

options 可以用来自定义复制操作。例如,要覆盖目标目录中的现有文件,请使用 copyFolder(source, target, StandardCopyOption.REPLACE_EXISTING);


因为所述的原因比许多其他答案要好得多。应该提到一个缺点:当安全管理器阻止访问某个文件时,无法访问文件的副本将会悄无声息地失败。(然而这种情况应该很少发生,通常不使用安全管理器。) - Olivier Cailloux
如果没有符号链接,这个解决方案是可以的,否则它会复制目标目录并将链接转换为一个空目录,只保留链接名称。 - Chris Wolf

16

以下代码怎么样?

public void copyFolder(File src, File dest) throws IOException {
        try (Stream<Path> stream = Files.walk(src.toPath())) {
            stream.forEachOrdered(sourcePath -> {

                try {
                    Files.copy(
                            /*Source Path*/
                            sourcePath,
                            /*Destination Path */
                            src.toPath().resolve(dest.toPath().relativize(sourcePath)));
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        }
    }

4
使用Path API 可以更优雅地确定目标路径:Path target = targetDir.resolve(sourceDir.relativize(path)) - bowmore
你好,Carl, 复制文件时顺序有什么影响吗?我想forEach应该已经足够了。 - Mohit Kanwar
2
请勿捕获异常并打印堆栈跟踪。将IOException包装为UncheckedIOException是适当的。 - Olivier Cailloux
@OlivierCailloux 是的,那更合适。 - Mohit Kanwar

10

这个版本使用Java 8建议的Files.walkPath参数。

public static void copyFolder(Path src, Path dest) {
    try {
        Files.walk( src ).forEach( s -> {
            try {
                Path d = dest.resolve( src.relativize(s) );
                if( Files.isDirectory( s ) ) {
                    if( !Files.exists( d ) )
                        Files.createDirectory( d );
                    return;
                }
                Files.copy( s, d );// use flag to override existing
            } catch( Exception e ) {
                e.printStackTrace();
            }
        });
    } catch( Exception ex ) {
        ex.printStackTrace();
    }
}

1

还有一个版本:

static void copyFolder(File src, File dest){
    // checks
    if(src==null || dest==null)
        return;
    if(!src.isDirectory())
        return;
    if(dest.exists()){
        if(!dest.isDirectory()){
            //System.out.println("destination not a folder " + dest);
            return;
        }
    } else {
        dest.mkdir();
    }

    if(src.listFiles()==null || src.listFiles().length==0)
        return;

    String strAbsPathSrc = src.getAbsolutePath();
    String strAbsPathDest = dest.getAbsolutePath();

    try {
        Files.walkFileTree(src.toPath(), new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file,
                    BasicFileAttributes attrs) throws IOException {
                File dstFile = new File(strAbsPathDest + file.toAbsolutePath().toString().substring(strAbsPathSrc.length()));
                if(dstFile.exists())
                    return FileVisitResult.CONTINUE;

                if(!dstFile.getParentFile().exists())
                    dstFile.getParentFile().mkdirs();

                //System.out.println(file + " " + dstFile.getAbsolutePath());
                Files.copy(file, dstFile.toPath());

                return FileVisitResult.CONTINUE;
            }
        });

    } catch (IOException e) {
        //e.printStackTrace();
        return;
    }

    return;
}

它的代码使用了Java8的Files.walkFileTree函数。


0

我的版本:

static private void copyFolder(File src, File dest) {
    // checks
    if(src==null || dest==null)
        return;
    if(!src.isDirectory())
        return;
    if(dest.exists()){
        if(!dest.isDirectory()){
            //System.out.println("destination not a folder " + dest);
            return;
        }
    } else {
        dest.mkdir();
    }

    File[] files = src.listFiles();
    if(files==null || files.length==0)
        return;

    for(File file: files){
        File fileDest = new File(dest, file.getName());
        //System.out.println(fileDest.getAbsolutePath());
        if(file.isDirectory()){
            copyFolder(file, fileDest);
        }else{
            if(fileDest.exists())
                continue;

            try {
                Files.copy(file.toPath(), fileDest.toPath());
            } catch (IOException e) {
                //e.printStackTrace();
            }
        }
    }
}

1
请考虑添加一些关于代码的解释。 - LittlePanda
我添加了一些昂贵的检查。如果外部代码中没有检查,例如来自用户的原始数据,则这很有用。 - pwipo
@pwipo 我只是想感谢您提供的这段代码,我发现大多数示例都无法正常工作,但这个可以完美执行,通过自动化您为我节省了很多时间,非常感谢!! - Tom
真的吗?你要在同一个对象上多次调用listFiles()吗??? - Blake McBride
请纠正我,@blake-mcbride,但我不明白他是如何在同一对象上调用listFiles()的?它在for循环之外,并且仅在较低目录的递归中再次调用... - JohnRDOrazio
显然,它现在没有被多次调用......(我的评论是在2/16发表的。两天后进行了修改.....) - Blake McBride

-1

可用于将源(文件或目录)复制到目标(目录)

void copy(Path source, Path target, boolean override) throws IOException {
    Path target = target.resolve(source.toFile().getName());
    Files.walkFileTree(source, new FileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            Path targetDir = target.resolve(source.relativize(dir));
            if(Files.notExists(targetDir)) {
                Files.createDirectory(targetDir);
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Files.copy(file, target.resolve(source.relativize(file))));
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            throw new RuntimeException("Copying file " + file + " failed", exc);
            // Consider looking at FileVisitResult options... 
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            if (exc != null) {
                // TODO...
            }
            return FileVisitResult.CONTINUE; // Or whatever works for you
        }
    });
}

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