前言
檔案複製/移動是基礎操作,但細節決定成敗:大檔案效能、屬性保留、原子性、跨檔案系統、符號連結處理。本文系統比較三種主流方式:Files.copy/move (NIO.2 標準)、FileInputStream/FileOutputStream (傳統串流)、FileUtils.copyFile/moveFile (Apache Commons IO),並給出生產級最佳實踐。
方式一:Files.copy / Files.move (JDK 7+ 推薦標準)
基本複製
Path src = Paths.get("/data/source.txt");
Path dst = Paths.get("/backup/source.txt");
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
保留屬性 (時間戳、權限、Owner)
Files.copy(src, dst,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES,
LinkOption.NOFOLLOW_LINKS); // 不追蹤符號連結
移動 (同檔案系統 = 原子重命名;跨檔案系統 = 複製+刪除)
Files.move(src, dst,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE); // 要求原子性,跨 FS 會拋 AtomicMoveNotSupportedException
遞迴複製目錄
Files.walkFileTree(srcDir, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path target = dstDir.resolve(srcDir.relativize(dir));
Files.createDirectories(target);
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dstDir.resolve(srcDir.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
方式二:傳統串流複製 (精細控制緩衝區、進度回調)
public static void copyWithProgress(File src, File dst, ProgressCallback cb) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[64 * 1024]; // 64KB
long total = src.length();
long copied = 0;
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
copied += len;
if (cb != null) cb.onProgress(copied, total);
}
out.flush();
}
}
@FunctionalInterface
interface ProgressCallback {
void onProgress(long copied, long total);
}
- ✅ 支援進度條、限速、斷點續傳邏輯。
- ❌ 需手動處理屬性複製 (
Files.setLastModifiedTime 等)。
- ❌ 大檔案效能略低於
Files.copy (缺乏零拷貝優化)。
方式三:Apache Commons IO (一行程式碼、內建重試)
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
FileUtils.copyFile(srcFile, dstFile); // 自動建立父目錄
FileUtils.copyDirectory(srcDir, dstDir);
FileUtils.moveFile(srcFile, dstFile); // 跨 FS 自動複製+刪除
FileUtils.moveDirectory(srcDir, dstDir);
- ✅ API 簡潔、自動建立目錄、內建重試機制 (Windows 檔案鎖定)。
- ❌ 依賴第三方 JAR;大檔案無零拷貝優化。
- ❌ 不支援
ATOMIC_MOVE、COPY_ATTRIBUTES 細粒度控制。
效能比較 (複製 1GB 檔案,SSD,JDK 17)
| 方式 | 耗時 | CPU 佔用 | 備註 |
| Files.copy (零拷貝) | ~1.2s | 低 | Linux copy_file_range/Windows CopyFileEx |
| Files.copy (緩衝串流) | ~1.8s | 中 | 無零拷貝支援時退回 |
| FileInputStream 64KB | ~2.1s | 中高 | 用戶態拷貝 |
| Commons IO | ~2.0s | 中高 | 內部 4KB 緩衝 |
關鍵選項對照表
| 需求 | NIO.2 選項 | Commons IO | 傳統串流 |
| 覆寫既有 | REPLACE_EXISTING | 預設覆寫 | FileOutputStream 預設 |
| 保留時間/權限 | COPY_ATTRIBUTES | 不支援 | 手動 setLastModified |
| 原子移動 | ATOMIC_MOVE | 不支援 | 不可行 |
| 不追蹤符號連結 | NOFOLLOW_LINKS | 預設不追蹤 | 需自行判斷 |
| 跨檔案系統移動 | 自動複製+刪除 | 自動複製+刪除 | 手動實作 |
生產級最佳實踐
- 預設首選
Files.copy/move:零拷貝、原子性、屬性保留、標準 API 無額外依賴。
- 需進度條/限速:自訂串流複製,緩衝區 64KB-256KB,配合
RateLimiter (Guava/Resilience4j)。
- 跨檔案系統移動大目錄:
Files.walkFileTree + 並行流 (Files.list(dir).parallelStream()) 複製,最後刪除來源。
- Windows 檔案鎖定重試:Commons IO 內建;NIO.2 需自行包裝重試邏輯 (捕獲
FileSystemException、Thread.sleep 重試)。
- 符號連結策略:備份/同步建議
NOFOLLOW_LINKS 保留連結結構;部署/發布建議追蹤連結複製實體檔案。
- 資源清理:所有 Stream 必須 try-with-resources;NIO.2
Files.newInputStream 同理。
常見錯誤與解法
AtomicMoveNotSupportedException:跨磁碟分區移動。解法:移除 ATOMIC_MOVE 或先複製再刪除。
DirectoryNotEmptyException:移動非空目錄到既有目標。解法:先刪除目標或使用 REPLACE_EXISTING (僅檔案)。
FileSystemException: The process cannot access the file (Windows):檔案被其他程式開啟。解法:重試機制、Volume Shadow Copy (VSS)、或排程離線處理。
- 權限不足:Linux 需
rw 來源、wx 目標目錄;Windows 需 NTFS Modify 權限。