Java 8:递归复制目录?

Posted

技术标签:

【中文标题】Java 8:递归复制目录?【英文标题】:Java 8: Copy directory recursively? 【发布时间】:2015-05-18 13:04:07 【问题描述】:

我看到 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 有什么改进吗?

【问题讨论】:

仅供参考 Files.readAllBytes(Paths.get(new URI(someUrl)) 自 Java 7 起可用。 else block 可以用java.nio.file.Files#copy(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption...) 完成,也可以从 1.7 开始 查看FileVisitor的javadoc中的例子:docs.oracle.com/javase/7/docs/api/java/nio/file/… 这个org.apache.commons.io.FileUtils.copyDirectory(File, File) 怎么样......它可以确保工作安全......:D 【参考方案1】:

这样代码看起来简单一些

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

【讨论】:

它确实会复制子文件夹及其内容。 –1:流永远不会关闭。 ***.com/a/60621544 更好。 你在说什么流?该解决方案不使用流。 docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/… 我删除了 -1 (但我仍然更喜欢this 答案,原因在那里说明)。【参考方案2】:

使用Files.walkFileTree

您无需担心关闭 Streams。 (这里的一些其他答案忘记了在使用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)));
                return FileVisitResult.CONTINUE;
            

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

这是做什么的

递归遍历目录中的所有文件。 遇到目录时 (preVisitDirectory): 在目标目录下创建对应的。 遇到常规文件时 (visitFile): 复制它。

options 可用于根据您的需要定制副本。例如要覆盖目标目录中的现有文件,请使用copyFolder(source, target, StandardCopyOption.REPLACE_EXISTING);

【讨论】:

由于上述原因,比许多其他答案要好得多。应该提到的一个缺点:当安全管理器阻止访问某些文件时,无法访问的文件的副本将静默失败。 (但这应该很少见,通常不使用安全管理器。) 如果没有符号链接,这个解决方案是可以的,否则,它将复制它们的目标目录并将链接变成一个带有链接名称的空目录。 如果要关注符号链接,可以将Files.walkFileTree(source, visitor)替换为Files.walkFileTree(source, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, visitor)【参考方案3】:

下面的代码怎么样

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

【讨论】:

使用 Path API 可以更优雅地确定目标路径:Path target = targetDir.resolve(sourceDir.relativize(path)) 您好 Carl,在复制文件时顺序有什么影响?我想 forEach 应该已经足够了。 请不要捕获异常和 printStackTrace。将 IOException 包装成 UncheckedIOException 是合适的。 @OlivierCailloux 是的,这更贴切。【参考方案4】:

此版本使用 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();
    

【讨论】:

【参考方案5】:

还有一个版本:

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 函数。

【讨论】:

【参考方案6】:

我的版本:

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

【讨论】:

请考虑添加一些关于代码的解释。 我添加了一些昂贵的支票。如果没有检查外部代码,它很有用,例如 - 来自用户的原始数据。 @pwipo 我只是想感谢您提供此代码,我发现的大多数示例都不起作用,但这完美无缺,您通过自动化为我节省了很多时间,谢谢! ! 真的吗?你要在同一个对象上多次调用 listFiles() 吗??? 如果我弄错了@blake-mcbride,请纠正我,但我看不到他是如何在同一个对象上调用 listFiles() 的?它在 for 循环之外,仅在较低目录中再次调用以进行递归...【参考方案7】:

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

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

【讨论】:

以上是关于Java 8:递归复制目录?的主要内容,如果未能解决你的问题,请参考以下文章

java 使用文件库递归复制目录树

一个Java复制目录的方法(递归)

Java中递归复制文件夹及文件(简易版)

Java中递归复制文件夹及文件(简易版)

使用java递归方法遍历指定目录下所有子目录和子文件

一个Java递归删除目录的方法