384. Java IO API - Java 文件復(fù)制工具:Copy 示例完整解析

384. Java IO API - Java 文件復(fù)制工具:Copy 示例完整解析

這是一個用 Java 編寫的遞歸文件復(fù)制工具,模仿了文件系統(tǒng)復(fù)制的操作。它從源目錄復(fù)制文件到目標(biāo)目錄,并支持指定復(fù)制的最大目錄層級(-depth)。

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

?? 功能簡介

該程序會:

  • 遞歸地將源目錄中的文件復(fù)制到目標(biāo)目錄。
  • 允許通過 -depth 參數(shù)指定最大目錄層級。
  • 在程序結(jié)束時,打印成功復(fù)制的文件數(shù)。

例如:

$ java Copy . new -depth 4

會把當(dāng)前目錄及其子目錄(最多 4 層)中的文件復(fù)制到 new 目錄。


? 示例代碼結(jié)構(gòu)詳解

1?? main() 方法:程序入口

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();
}
  • 輸入?yún)?shù)解析:首先檢查輸入是否符合規(guī)范,若不符合則調(diào)用 usage() 打印使用說明。
  • 路徑和深度解析:從命令行參數(shù)中提取源目錄、目標(biāo)目錄和最大目錄深度。
  • 文件遍歷:使用 Files.walkFileTree() 遞歸遍歷源目錄,進(jìn)行復(fù)制操作。

2?? Replicator 類:文件復(fù)制核心邏輯

public static class Replicator extends SimpleFileVisitor<Path> {

該類繼承 SimpleFileVisitor<Path> 并重寫了幾個關(guān)鍵方法來執(zhí)行復(fù)制操作。

?? 構(gòu)造方法:初始化源路徑與目標(biāo)路徑

public Replicator(Path source, Path destination) {
    this.source = source;
    this.destination = destination;
}
  • source:源目錄路徑;
  • destination:目標(biāo)目錄路徑。

?? visitFile() 方法:復(fù)制文件

@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() 復(fù)制文件到目標(biāo)路徑。
  • source.relativize(file) 計算文件的相對路徑,以保持源文件夾結(jié)構(gòu)。
  • 如果復(fù)制失敗,會捕獲并記錄異常,但繼續(xù)執(zhí)行。

?? preVisitDirectory() 方法:復(fù)制目錄

@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() 會復(fù)制目錄及其屬性。
  • 如果目錄已存在并且指定了 REPLACE_EXISTING,它將覆蓋該目錄。
  • 如果復(fù)制失敗,打印錯誤信息并跳過該子目錄(使用 SKIP_SUBTREE)。

?? postVisitDirectory() 方法:復(fù)制目錄屬性

@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;
}
  • 在目錄復(fù)制后,復(fù)制目錄的最后修改時間等屬性。
  • 如果復(fù)制失敗,打印錯誤信息。

?? visitFileFailed() 方法:錯誤處理

@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;
}
  • 處理訪問失敗的文件。
  • 如果檢測到文件系統(tǒng)循環(huán)(符號鏈接等),打印循環(huán)錯誤信息。

3?? done() 方法:統(tǒng)計已復(fù)制文件數(shù)

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());
    }
}
  • 在文件復(fù)制完成后,統(tǒng)計并打印成功復(fù)制的文件數(shù)。

4?? usage() 方法:程序使用說明

static void usage() {
    System.err.println("java Copy <source> <destination> -depth \"<max_level_dir>\"");
    System.exit(-1);
}
  • 如果輸入?yún)?shù)無效,打印使用說明并退出程序。

?? 擴(kuò)展思路與練習(xí)建議

擴(kuò)展功能 實現(xiàn)方法
復(fù)制文件時跳過某些類型 visitFile() 方法中加入文件類型判斷(如跳過 .txt 文件)
并行復(fù)制大文件夾 使用 ExecutorService 執(zhí)行并行文件復(fù)制
復(fù)制過程中顯示進(jìn)度條 使用 System.out.print 打印進(jìn)度信息
按文件類型篩選復(fù)制 visitFile() 中加入文件類型過濾,例如只復(fù)制 .java 文件
備份和版本控制 visitFile() 中實現(xiàn)版本號控制,避免覆蓋舊版本

?? 錯誤處理建議

  • IOException 提供更加詳細(xì)的日志,例如記錄文件復(fù)制失敗的具體原因,或者保存失敗文件列表。
  • visitFileFailed() 方法添加更多的錯誤類型處理,如權(quán)限不足等。

? 結(jié)語

這個 Copy 示例演示了如何使用 Java 的 nio 包進(jìn)行遞歸文件復(fù)制。通過自定義 FileVisitor 類,你可以實現(xiàn)高效且靈活的文件操作。這個工具可以被擴(kuò)展和定制化,滿足各種實際項目中的需求。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容