首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 複製與移動檔案的完整指南:NIO.2、Stream、Apache Commons IO 比較

     編輯:Java 相關知識

前言

檔案複製/移動是基礎操作,但細節決定成敗:大檔案效能、屬性保留、原子性、跨檔案系統、符號連結處理。本文系統比較三種主流方式: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預設不追蹤需自行判斷
跨檔案系統移動自動複製+刪除自動複製+刪除手動實作

生產級最佳實踐

  1. 預設首選 Files.copy/move:零拷貝、原子性、屬性保留、標準 API 無額外依賴。
  2. 需進度條/限速:自訂串流複製,緩衝區 64KB-256KB,配合 RateLimiter (Guava/Resilience4j)。
  3. 跨檔案系統移動大目錄:Files.walkFileTree + 並行流 (Files.list(dir).parallelStream()) 複製,最後刪除來源。
  4. Windows 檔案鎖定重試:Commons IO 內建;NIO.2 需自行包裝重試邏輯 (捕獲 FileSystemException、Thread.sleep 重試)。
  5. 符號連結策略:備份/同步建議 NOFOLLOW_LINKS 保留連結結構;部署/發布建議追蹤連結複製實體檔案。
  6. 資源清理:所有 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 權限。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved