将整个目录内容复制到另一个目录?

90

如何在Java或Groovy中将整个目录的内容复制到另一个目录?


你需要一个命令行工具还是代码? - Buhake Sindi
10个回答

124

FileUtils.copyDirectory()

该方法可以将整个目录复制到新位置并保留文件日期。它会将指定的目录及其所有子目录和文件复制到指定的目标路径中,目标路径将成为新的目录名称和位置。

如果目标目录不存在,则创建目标目录。如果目标目录已存在,则此方法会将源目录与目标目录合并,以源目录为优先。

以下是示例代码:

String source = "C:/your/source";
File srcDir = new File(source);

String destination = "C:/your/destination";
File destDir = new File(destination);

try {
    FileUtils.copyDirectory(srcDir, destDir);
} catch (IOException e) {
    e.printStackTrace();
}

24
在我的情况下,我有一些子文件夹,我也想复制它们的结构,并找到了方法FileUtils.copyDirectoryStructure()。也许这对其他人也有帮助。 - Ethan Leroy
4
JAVA API NIO 2怎么样?我尝试使用Files.copy(Path, Path),但似乎并没有完成相同的工作。 - herau
1
正如您在这里看到的,“目录可以被复制。但是,目录中的文件不会被复制,因此新目录为空。” http://docs.oracle.com/javase/tutorial/essential/io/copy.html - NoBugs
这不使用Java NIO,因此无法从ZipFileSystem复制。 - tribbloid
5
import org.apache.commons.io.FileUtils - Jean Bob
显示剩余3条评论

34

以下是使用JDK7的示例。

public class CopyFileVisitor extends SimpleFileVisitor<Path> {
    private final Path targetPath;
    private Path sourcePath = null;
    public CopyFileVisitor(Path targetPath) {
        this.targetPath = targetPath;
    }

    @Override
    public FileVisitResult preVisitDirectory(final Path dir,
    final BasicFileAttributes attrs) throws IOException {
        if (sourcePath == null) {
            sourcePath = dir;
        } else {
        Files.createDirectories(targetPath.resolve(sourcePath
                    .relativize(dir)));
        }
        return FileVisitResult.CONTINUE;
    }

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

要使用访问者,请按照以下步骤进行

Files.walkFileTree(sourcePath, new CopyFileVisitor(targetPath));

如果您更喜欢将所有内容内联(如果您经常使用它就不太高效,但适用于快速操作)

    final Path targetPath = // target
    final Path sourcePath = // source
    Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(final Path dir,
                final BasicFileAttributes attrs) throws IOException {
            Files.createDirectories(targetPath.resolve(sourcePath
                    .relativize(dir)));
            return FileVisitResult.CONTINUE;
        }

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

实际上,这个工作有一个来自Oracle的访问者:http://docs.oracle.com/javase/tutorial/essential/io/examples/Copy.java - 你的代码和这个有什么区别? - Mr_and_Mrs_D
2
我的只是复制文件,他们的是一个完整的应用程序,可以复制属性。 - Archimedes Trajano
非常好的解决方案!我想向其他读者指出这种方法不太明显的优点之一。如果您使用jar FileSystem路径,此方法可以用于复制文件进入和退出jar文件。 - Tom Rutchik

16

使用 Groovy,你可以利用 Ant 来完成以下操作:

new AntBuilder().copy( todir:'/path/to/destination/folder' ) {
  fileset( dir:'/path/to/src/folder' )
}

AntBuilder是Groovy发行版的一部分,也是自动导入列表中的一项,这意味着它对于任何Groovy代码都是直接可用的。


8
你可能可以用Java实现,但这就像用大锤来砸核桃一样过于复杂了。 - Stephen C
1
也许吧,但在Groovy中,AntBuilder是分发的一部分,并且自动导入列表中,这意味着它对于任何按照答案编写的Groovy代码都直接可用。 - Matias Bjarland

13
public static void copyFolder(File source, File destination)
{
    if (source.isDirectory())
    {
        if (!destination.exists())
        {
            destination.mkdirs();
        }

        String files[] = source.list();

        for (String file : files)
        {
            File srcFile = new File(source, file);
            File destFile = new File(destination, file);

            copyFolder(srcFile, destFile);
        }
    }
    else
    {
        InputStream in = null;
        OutputStream out = null;

        try
        {
            in = new FileInputStream(source);
            out = new FileOutputStream(destination);

            byte[] buffer = new byte[1024];

            int length;
            while ((length = in.read(buffer)) > 0)
            {
                out.write(buffer, 0, length);
            }
        }
        catch (Exception e)
        {
            try
            {
                in.close();
            }
            catch (IOException e1)
            {
                e1.printStackTrace();
            }

            try
            {
                out.close();
            }
            catch (IOException e1)
            {
                e1.printStackTrace();
            }
        }
    }
}

它是Groovy吗?它看起来比Java更像C ++。 :-). 但它似乎是正确的。 +1。如果我们复制并同时处理被复制的文本,那就太好了。 - Gangnus
工作正常,简单易用,非常不错!这种解决方案与其他方案唯一的区别可能在于,复制文件的修改日期和创建日期都被设置为当前时间,但有时这也是您想要的。 - Perry Monschau
3
虽然我已经从事Java编程工作20年了,但我仍像以前C和C ++程序员一样在新的行上看到花括号的语法。虽然这有点老派,但更易读! - DAB
我认为这是更清晰的方法。加上进度条怎么样? - Noor Hossain
生命救星,我在 Kotlin 中使用它,完美运作 +2。 - Amir

8
这是我用Groovy编写的代码。已经测试过。
private static void copyLargeDir(File dirFrom, File dirTo){
    // creation the target dir
    if (!dirTo.exists()){
        dirTo.mkdir();
    }
    // copying the daughter files
    dirFrom.eachFile(FILES){File source ->
        File target = new File(dirTo,source.getName());
        target.bytes = source.bytes;
    }
    // copying the daughter dirs - recursion
    dirFrom.eachFile(DIRECTORIES){File source ->
        File target = new File(dirTo,source.getName());
        copyLargeDir(source, target)
    }
}

2
这种方法比FileUtils.copyDirectory()更好在哪里? - doelleri
4
@doelleri,这个方案比其他方案更好,有两点原因。第一点是我不需要安装任何额外的jar包或在Maven中添加引用,也不需要为版本号纠结。顺便说一句,“答案”应该包含适当的引用行和jar包的参考。第二个原因是,如果我想对子目录进行严格的过滤,只有我的代码能帮到我。第三个原因是,这里是为程序员而设的网站,而不仅仅是软件用户。 :-) - Gangnus
如果你想知道FILES和DIRECTORIES的来源,你可以使用groovy.io.FileType.FILES和groovy.io.FileType.DIRECTORIES来替换它们。 - Luke Machowski

5

19
澄清“Java 7:看看java.nio.file.Files” - 实际上并没有回答这个问题。 - Mr_and_Mrs_D
7
虽然 Files.copy 支持目录,但它不会复制目录中的内容。 - Max
2
Apache是最好的。+1 - OhadR
2
@OhadR 我会说“更简单”而不是“更好”。 - Stephan

4

随着Java NIO的推出,以下是可能的解决方案

使用Java 9:

private static void copyDir(String src, String dest, boolean overwrite) {
    try {
        Files.walk(Paths.get(src)).forEach(a -> {
            Path b = Paths.get(dest, a.toString().substring(src.length()));
            try {
                if (!a.toString().equals(src))
                    Files.copy(a, b, overwrite ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    } catch (IOException e) {
        //permission issue
        e.printStackTrace();
    }
}

使用Java 7:

import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Test {

    public static void main(String[] args) {
        Path sourceParentFolder = Paths.get("/sourceParent");
        Path destinationParentFolder = Paths.get("/destination/");

        try {
            Stream<Path> allFilesPathStream = Files.walk(sourceParentFolder);
            Consumer<? super Path> action = new Consumer<Path>(){

                @Override
                public void accept(Path t) {
                    try {
                        String destinationPath = t.toString().replaceAll(sourceParentFolder.toString(), destinationParentFolder.toString());
                        Files.copy(t, Paths.get(destinationPath));
                    } 
                    catch(FileAlreadyExistsException e){
                        //TODO do acc to business needs
                    }
                    catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }

            };
            allFilesPathStream.forEach(action );

        } catch(FileAlreadyExistsException e) {
            //file already exists and unable to copy
        } catch (IOException e) {
            //permission issue
            e.printStackTrace();
        }

    }

}

1
为什么要踩我?如果需要改进,请提出建议。 - Mohit Kanwar
字符串替换可能会导致意外的副作用。您应该只替换我们知道存在并且知道其长度的开头,因此您可以使用:Paths.get(dest,a.toString()。substring(src.length()))。此外,您可以进行一些优化:可以删除重复的FileAlreadyExistsException子句,您仅使用源和目标路径对象的一个用法,因此无需为每个变量设置一个。 - Charlie
同意,谢谢 @Charlie - Mohit Kanwar
我本想建议用lambda替换一些样板代码,但这会对寻找Java 7(6?)答案的人造成困扰。 - Charlie
我本来想添加自己的答案,但这个问题被关闭为重复(尽管它并不是)。我将使用我的Java 9版本补充您的答案。如果您不喜欢,请删除它;) - Charlie
抱歉打扰了,原始实现还有另一个问题;它会抛出一些异常(尽管实际上已经完成了复制),因为遍历的目录顶层(./)被包含在结果流中,导致 java.nio.file.DirectoryNotEmptyException 异常。请查看我的更新版本以进行检查并随意添加到您的原始版本中。 - Charlie

3

1
请修正链接(方法在此处:http://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/FileUtils.html#copyDirectory%28java.io.File,%20java.io.File%29),但我猜您链接到了代码。 - Mr_and_Mrs_D
@Mr_and_Mrs_D,已更正。谢谢! - Gili

0
如果您愿意使用第三方库,请查看javaxt-core。javaxt.io.Directory类可用于像这样复制目录:
javaxt.io.Directory input = new javaxt.io.Directory("/source");
javaxt.io.Directory output = new javaxt.io.Directory("/destination");
input.copyTo(output, true); //true to overwrite any existing files

您还可以提供一个文件过滤器来指定要复制哪些文件。这里有更多的示例:

http://javaxt.com/javaxt-core/io/Directory/Directory_Copy


但是如何将它添加到Android项目中?依赖项在哪里? - Amir

0

Files.copy 不支持复制目录内容。 - Max

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