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 が入っている(またはその逆)。

実例

テキストファイルにタスク一覧を保存するアプリを作っているとします。プログラムは「1行=1タスク」を想定しています。ところがユーザーがファイルを 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";

        // 例として: 5個ではなく3個の数値を書き込むファイルを作成
        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. データ復旧: 戦略

正常な部分データの読み出し

ファイル構造がそれを許すなら、エラーまでに読み取れたデータだけでも「引き出す」ことができます。たとえば、ファイルが行の一覧(1行=1タスク)であれば、障害が起きるまでの行を処理できます。

try (BufferedReader reader = new BufferedReader(new FileReader("tasks.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 行を処理する
    }
} catch (IOException e) {
    System.err.println("読み取りエラー: " + e.getMessage());
    // 既に読み取れたデータを保存する、またはユーザーに復旧を提案する
}

バックアップファイルの利用

事前にファイルのコピー(たとえば 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: 復旧についての周知不足。 ユーザーは、ファイルが破損してバックアップから復元されたことを知るべきです。そうでないと、なぜ一部のデータが消えたのか理解できません。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION