CodeGym /課程 /JAVA 25 SELF /處理損壞的檔案,資料復原

處理損壞的檔案,資料復原

JAVA 25 SELF
等級 38 , 課堂 2
開放

1. 損壞檔案的徵兆

在理想的世界裡,檔案總能無錯地讀寫,其中的資料就像剛出爐的麵包:柔軟、香氣四溢、整齊、完好。但在現實中,檔案可能會「壞掉」、「走樣」、「只寫了一半」或「格式不對」。原因可能很多,例如在寫入磁碟時發生故障(突然斷電)、透過網路傳輸時的錯誤、儲存媒體損壞(那個老掉牙的 bad sector)。或者,用不合適的程式手動編輯檔案也會損壞檔案,還有預期的資料格式與實際格式不一致等情形。

在 Java 中,這些情況通常會在讀取/寫入時以例外的形式表現,有時也會透過程式的怪異行為顯現(例如資料突然結束,或出現一堆亂碼)。

讀取時的例外

最明顯的跡象是非預期的例外。以下是一些常見情況:

  • EOFException — 檔案意外結束(End Of File)。你預期檔案中還有資料,但實際上沒有。
  • MalformedInputException(或在較舊的 API 中,來自 NIO 的 MalformedInputException)— 檔案不符合預期的編碼或結構。
  • ZipException — 當你把壓縮檔當一般檔案讀取時。
  • StreamCorruptedException — 讀取序列化物件時,若檔案已損壞。

資料格式不相符

有時檔案讀取時不會丟出例外,但內容並不符合預期的格式:

  • 預期是一行字串,結果得到一堆難以辨識的符號(亂碼)。
  • 預期有特定數量的數字,但實際上比較少。
  • 預期是 CSV 檔,結果是 JSON(或反之亦然)。

實際範例

假設你寫了一個應用程式,用文字檔儲存任務清單。程式假定每一行就是一個任務。但使用者把檔案用 Excel 開啟、修改,並以另一種格式儲存……結果你的程式就讀不出這個檔案了。

2. 處理損壞檔案的策略

記錄日誌並告知使用者

第一條規則:別恐慌!(也別讓使用者恐慌)。請務必記錄錯誤,並在發生問題時通知使用者。但沒必要把 Java 堆疊追蹤的所有恐怖細節都展示給他。

try {
    // 讀取檔案
} catch (EOFException e) {
    System.err.println("檔案意外結束。可能已損壞。");
    // 記錄詳細資訊
    e.printStackTrace();
}

嘗試部分復原

有時至少可以「救回」部分資料。比如逐行讀取時,可以處理第一個錯誤之前的所有行。

使用備份(backup)

嚴謹的程式常會在寫入前替重要檔案建立備份。如果主要檔案損壞,可以嘗試從備份復原資料。

3. 實作:讀取意外結尾(EOF)的檔案

典型情況

假設有一個二進位檔,按順序寫入 int 整數。程式預期剛好有 5 個,但檔案已損壞,實際只寫了 3 個。

import java.io.*;

public class DamagedFileExample {
    public static void main(String[] args) {
        String filename = "numbers.bin";

        // 作為示例:建立只含 3 個數字的檔案(本該是 5 個)
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(filename))) {
            out.writeInt(42);
            out.writeInt(7);
            out.writeInt(2024);
            // out.writeInt(1); out.writeInt(2); // 刻意不寫入!
        } catch (IOException e) {
            System.err.println("建立檔案時出錯:" + e.getMessage());
        }

        // 現在嘗試讀取 5 個數字
        try (DataInputStream in = new DataInputStream(new FileInputStream(filename))) {
            for (int i = 0; i < 5; i++) {
                int number = in.readInt();
                System.out.println("讀取到數字:" + number);
            }
        } catch (EOFException e) {
            System.err.println("檔案意外結束!可能已損壞。");
        } catch (IOException e) {
            System.err.println("讀取錯誤:" + e.getMessage());
        }
    }
}

輸出:

讀取到數字: 42
讀取到數字: 7
讀取到數字: 2024
檔案意外結束!可能已損壞。

讀到第一個錯誤為止

常見的做法是在迴圈中持續讀取,直到拋出例外為止。這樣至少能取得部分資訊。

try (DataInputStream in = new DataInputStream(new FileInputStream(filename))) {
    while (true) {
        try {
            int number = in.readInt();
            System.out.println("讀取到數字:" + number);
        } catch (EOFException e) {
            System.out.println("資料已結束(或檔案已損壞)。");
            break;
        }
    }
} catch (IOException e) {
    System.err.println("讀取錯誤:" + e.getMessage());
}

4. 處理文字檔與編碼

編碼問題

如果檔案用一種編碼寫入,卻用另一種編碼讀取,可能會發生解碼錯誤:

import java.nio.charset.*;

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("tasks.txt"), "UTF-8"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (MalformedInputException e) {
    System.err.println("編碼錯誤!檔案已損壞或不是以 UTF-8 寫入。");
} catch (IOException e) {
    System.err.println("讀取錯誤:" + e.getMessage());
}

重要:有時你不會得到例外,而是看到「亂碼」——這也是檔案損壞或編碼不正確的徵兆。

如何處理?

  • 告知使用者問題所在。
  • 嘗試用其他編碼開啟檔案。
  • 若資料很重要——建議從備份復原。

5. 資料復原:策略

讀取部分可用的資料

如果檔案結構允許,可以先「撈出」在錯誤發生前成功讀到的資料。例如檔案是行清單(每行一個任務),就能處理所有出錯前的行。

try (BufferedReader reader = new BufferedReader(new FileReader("tasks.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 處理該行
    }
} catch (IOException e) {
    System.err.println("讀取錯誤:" + e.getMessage());
    // 可以先保存已讀取的資料,或提示使用者進行復原
}

使用 backup 檔案

如果你事先建立了檔案副本(例如 tasks.txt.bak),就可以從中復原資料:

File original = new File("tasks.txt");
File backup = new File("tasks.txt.bak");

if (!original.exists() && backup.exists()) {
    // 把備份複製回原位置
    Files.copy(backup.toPath(), original.toPath(), StandardCopyOption.REPLACE_EXISTING);
    System.out.println("已從備份成功復原。");
}

雜湊檢核與驗證

對重要檔案,可以保存校驗和(例如 MD5 或 SHA-256),並在每次開啟時與最新的校驗值比對。若不一致,代表檔案已損壞。

// 範例流程(為簡化起見省略雜湊實作)
String expectedHash = "..."; // 先前保存的校驗值
String actualHash = calculateFileHash("tasks.txt");
if (!expectedHash.equals(actualHash)) {
    System.out.println("檔案 tasks.txt 已損壞!請嘗試從備份復原。");
}

6. 處理損壞檔案時的常見錯誤

錯誤 №1:未檢查檔案格式。 如果你預期每一行都是例如數字,但實際上是文字,會拋出 NumberFormatException。最好在讀取的同時做驗證。

錯誤 №2:未使用 try-with-resources。 如果不使用 try-with-resources,即使出錯檔案也可能保持未關閉狀態,這會讓復原或刪除變得困難。

錯誤 №3:未先建立備份就覆寫損壞的檔案。 一旦出錯就立刻覆寫檔案,復原機率會降低。最好先保存備份。

錯誤 №4:缺乏資訊的復原流程。 應讓使用者知道檔案曾經損壞並已從備份復原——否則他可能不明白為什麼部分資料會消失。

1
任務
JAVA 25 SELF, 等級 38, 課堂 2
上鎖
解讀古老訊息:等待完整內容 🧩
解讀古老訊息:等待完整內容 🧩
1
任務
JAVA 25 SELF, 等級 38, 課堂 2
上鎖
安全稽核:驗證機密檔案的真實性 🛡️
安全稽核:驗證機密檔案的真實性 🛡️
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION