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ò)展和定制化,滿足各種實際項目中的需求。