384. Java IO API - Java 文件复制工具:Copy 示例完整解析

384. Java IO API - Java 文件复制工具:Copy 示例完整解析

这是一个用 Java 编写的递归文件复制工具,模仿了文件系统复制的操作。它从源目录复制文件到目标目录,并支持指定复制的最大目录层级(-depth)。

java 复制代码
The Copy Example
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.EnumSet;
import java.util.stream.Stream;

import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

/**
 * Sample code that copies files recursively 
 * from a source directory to a destination folder.
 * The maximum number of directory levels to copy 
 * is specified after -depth.
 * The number of files copied is printed
 * to standard out.
 * You can execute the application using:
 * @code java Copy . new -depth 4
 */

public class Copy {

    /**
     * A {@code FileVisitor} that finds
     * all files that match the
     * specified pattern.
     */
    public static class Replicator
            extends SimpleFileVisitor<Path> {

        Path source;
        Path destination;


        public Replicator(Path source, Path destination) {
            this.source = source;
            this.destination = destination;
        }

        // Prints the total number of
        // files copied to standard out.
        void done() throws IOException {
            try (Stream<Path> path = Files.list(Paths.get(destination.toUri()))) {
                System.out.println("Number of files copied: "
                        + path.filter(p -> p.toFile().isFile()).count());
            }

        }

        // Copy a file in destination
        @Override
        public FileVisitResult visitFile(Path file,
                                         BasicFileAttributes attrs) {
            System.out.println("Copy file: " + file);
            Path newFile = destination.resolve(source.relativize(file));
            try{
                Files.copy(file,newFile);
            }
            catch (IOException ioException){
                //log it and move
            }
            return CONTINUE;
        }

        // Invoke copy of a directory.
        @Override
        public FileVisitResult preVisitDirectory(Path dir,
                                                 BasicFileAttributes attrs) {
            System.out.println("Copy directory: " + dir);
            Path targetDir = destination.resolve(source.relativize(dir));
            try {
                Files.copy(dir, targetDir, REPLACE_EXISTING, COPY_ATTRIBUTES);
            } catch (IOException e) {
                System.err.println("Unable to create " + targetDir + " [" + e + "]");
                return SKIP_SUBTREE;
            }

            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            if (exc == null) {
                Path destination = this.destination.resolve(source.relativize(dir));
                try {
                    FileTime time = Files.getLastModifiedTime(dir);
                    Files.setLastModifiedTime(destination, time);
                } catch (IOException e) {
                    System.err.println("Unable to copy all attributes to: " + destination + " [" + e + "]");
                }
            } else {
                throw exc;
            }

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

    static void usage() {
        System.err.println("java Copy <source> <destination>" +
                " -depth \"<max_level_dir>\"");
        System.exit(-1);
    }

    public static void main(String[] args)
            throws IOException {

        if (args.length < 4 || !args[2].equals("-depth"))
            usage();

        Path source = Paths.get(args[0]);
        Path destination = Paths.get(args[1]);
        int depth = Integer.parseInt(args[3]);

        Replicator walk = new Replicator(source, destination);
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        Files.walkFileTree(source, opts, depth, walk);
        walk.done();
    }
}

📝 功能简介

该程序会:

  • 递归地将源目录中的文件复制到目标目录。
  • 允许通过 -depth 参数指定最大目录层级。
  • 在程序结束时,打印成功复制的文件数。

例如:

java 复制代码
$ java Copy . new -depth 4

会把当前目录及其子目录(最多 4 层)中的文件复制到 new 目录。


✅ 示例代码结构详解

1️⃣ main() 方法:程序入口

java 复制代码
public static void main(String[] args) throws IOException {
    if (args.length < 4 || !args[2].equals("-depth"))
        usage();

    Path source = Paths.get(args[0]);
    Path destination = Paths.get(args[1]);
    int depth = Integer.parseInt(args[3]);

    Replicator walk = new Replicator(source, destination);
    EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
    Files.walkFileTree(source, opts, depth, walk);
    walk.done();
}
  • 输入参数解析 :首先检查输入是否符合规范,若不符合则调用 usage() 打印使用说明。
  • 路径和深度解析:从命令行参数中提取源目录、目标目录和最大目录深度。
  • 文件遍历 :使用 Files.walkFileTree() 递归遍历源目录,进行复制操作。

2️⃣ Replicator 类:文件复制核心逻辑

java 复制代码
public static class Replicator extends SimpleFileVisitor<Path> {

该类继承 SimpleFileVisitor<Path> 并重写了几个关键方法来执行复制操作。

🧩 构造方法:初始化源路径与目标路径
java 复制代码
public Replicator(Path source, Path destination) {
    this.source = source;
    this.destination = destination;
}
  • source:源目录路径;
  • destination:目标目录路径。

🔍 visitFile() 方法:复制文件
java 复制代码
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
    System.out.println("Copy file: " + file);
    Path newFile = destination.resolve(source.relativize(file));
    try {
        Files.copy(file, newFile);
    } catch (IOException ioException) {
        // 错误日志处理
    }
    return CONTINUE;
}
  • 使用 Files.copy() 复制文件到目标路径。
  • source.relativize(file) 计算文件的相对路径,以保持源文件夹结构。
  • 如果复制失败,会捕获并记录异常,但继续执行。

📂 preVisitDirectory() 方法:复制目录
java 复制代码
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
    System.out.println("Copy directory: " + dir);
    Path targetDir = destination.resolve(source.relativize(dir));
    try {
        Files.copy(dir, targetDir, REPLACE_EXISTING, COPY_ATTRIBUTES);
    } catch (IOException e) {
        System.err.println("Unable to create " + targetDir + " [" + e + "]");
        return SKIP_SUBTREE;
    }
    return CONTINUE;
}
  • Files.copy() 会复制目录及其属性。
  • 如果目录已存在并且指定了 REPLACE_EXISTING,它将覆盖该目录。
  • 如果复制失败,打印错误信息并跳过该子目录(使用 SKIP_SUBTREE)。

🕓 postVisitDirectory() 方法:复制目录属性
java 复制代码
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    if (exc == null) {
        Path destination = this.destination.resolve(source.relativize(dir));
        try {
            FileTime time = Files.getLastModifiedTime(dir);
            Files.setLastModifiedTime(destination, time);
        } catch (IOException e) {
            System.err.println("Unable to copy all attributes to: " + destination + " [" + e + "]");
        }
    } else {
        throw exc;
    }
    return CONTINUE;
}
  • 在目录复制后,复制目录的最后修改时间等属性。
  • 如果复制失败,打印错误信息。

⚠️ visitFileFailed() 方法:错误处理
java 复制代码
@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;
}
  • 处理访问失败的文件。
  • 如果检测到文件系统循环(符号链接等),打印循环错误信息。

3️⃣ done() 方法:统计已复制文件数

java 复制代码
void done() throws IOException {
    try (Stream<Path> path = Files.list(Paths.get(destination.toUri()))) {
        System.out.println("Number of files copied: "
                + path.filter(p -> p.toFile().isFile()).count());
    }
}
  • 在文件复制完成后,统计并打印成功复制的文件数。

4️⃣ usage() 方法:程序使用说明

java 复制代码
static void usage() {
    System.err.println("java Copy <source> <destination> -depth \"<max_level_dir>\"");
    System.exit(-1);
}
  • 如果输入参数无效,打印使用说明并退出程序。

🧠 扩展思路与练习建议

扩展功能 实现方法
复制文件时跳过某些类型 visitFile() 方法中加入文件类型判断(如跳过 .txt 文件)
并行复制大文件夹 使用 ExecutorService 执行并行文件复制
复制过程中显示进度条 使用 System.out.print 打印进度信息
按文件类型筛选复制 visitFile() 中加入文件类型过滤,例如只复制 .java 文件
备份和版本控制 visitFile() 中实现版本号控制,避免覆盖旧版本

🔔 错误处理建议

  • IOException 提供更加详细的日志,例如记录文件复制失败的具体原因,或者保存失败文件列表。
  • visitFileFailed() 方法添加更多的错误类型处理,如权限不足等。

✅ 结语

这个 Copy 示例演示了如何使用 Java 的 nio 包进行递归文件复制。通过自定义 FileVisitor 类,你可以实现高效且灵活的文件操作。这个工具可以被扩展和定制化,满足各种实际项目中的需求。

相关推荐
shadowcz0071 小时前
Chrome Skills 来了:把你的 AI 提示词变成一键工具
前端·人工智能·chrome
踩着两条虫1 小时前
VTJ核心引擎开源项目概览
前端·vue.js·低代码
Front思1 小时前
解决 uniapp Dart Sass 2.0.0 弃用警告
前端·uni-app·sass
农夫山泉不太甜1 小时前
CSS 新特性与冷门属性深度剖析
前端
Hy行者勇哥1 小时前
Chrome 浏览器如何“网页长截图”和“网站打包成应用”
前端·chrome
霸道流氓气质1 小时前
SpringBoot中集成LangChain4j实现集成阿里百炼平台进行AI快速对话
人工智能·spring boot·后端·langchain4j
lolo大魔王2 小时前
Go语言的结构体
开发语言·后端·golang
说点AI2 小时前
我的 Vibe Coding 工具箱:一个人如何从零做出一个产品
前端·后端
SuperEugene2 小时前
Vue3 前端配置驱动避坑:配置冗余、渲染性能、扩展性问题解决|配置驱动开发实战篇
前端·javascript·vue.js·驱动开发·前端框架