CodeGym /コース /JAVA 25 SELF /バイナリファイルの読み書き: InputStream, OutputStream

バイナリファイルの読み書き: InputStream, OutputStream

JAVA 25 SELF
レベル 36 , レッスン 2
使用可能

1. 導入

まず基本から始めましょう。バイナリファイルはテキストファイルと何が違うのでしょうか。テキストファイルとは、普通のメモ帳で開いて文字や数字、スペースなどの記号が読めるファイルのことです。例えば、my_notes.txtpoem.txt です。

バイナリファイル は、テキストではなく任意のバイト列を含むファイルです。画像(.jpg.png)、音楽(.mp3)、アーカイブ(.zip)、実行ファイル(.exe)、動画(.mp4)、データベースファイルなどが該当します。こうしたファイルをメモ帳で開くと、ÿØÿà のようなものや意味不明な記号の羅列が見えるでしょう。これは正常です! コンピュータは「バイト」しか理解しません。コンピュータにとってテキストも画像も動画も、単なるバイト列なのです。テキストファイルではそのバイトを文字として解釈できますが、バイナリでは人間が読むための「生の」データであり、可読性は想定されていません。

バイナリファイル操作の基本クラス

Java ではバイナリファイルの操作にバイトストリームを使います:

  • InputStream — バイトを読み取るための基底クラス。
  • OutputStream — バイトを書き込むための基底クラス。

ファイルに対して使用する具体的な実装は次のとおりです:

  • FileInputStream — ファイルからバイトを読み取ります。
  • FileOutputStream — ファイルへバイトを書き込みます。

FileReaderFileWriter を聞いたことがあるかもしれませんが、これらは文字を扱うためのものでテキスト専用です。バイナリファイルには InputStream/OutputStream とそのサブクラスだけを使ってください

2. バイナリファイルの読み取り

1 バイトずつ読む

最も簡単な方法は、ファイルを 1 バイトずつ読むことです。分かりやすい反面、とても遅いです。

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b は 0 〜 255(バイト)の数値で、-1 はファイル終端を示します
        // バイトを処理できます。例えば全バイトの合計を計算するなど
    }
}

メソッド read() は次のバイトを int0 から 255)として返し、ファイルの終わりに到達すると -1 を返します。通常、1 バイトずつ読むのは、ファイル構造の解析など、非常に特別な目的がある場合だけです。

ブロック(バッファ)で読む

1 バイトずつ読むのは、リンゴを1個ずつ買いに行くようなものです。袋でまとめて買ったほうがずっと効率的です! Java には read(byte[] buffer) というメソッドがあり、配列をファイルからのバイトで埋めてくれます。

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // 4 KB のバッファ
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer にはファイルから bytesRead バイトが入っています
        // これらのバイトを処理できます。例えばどこか別の場所に保存するなど
    }
}

read(buffer) は実際に読み取れたバイト数を返します(特に最後の読み取りでは、バッファサイズより少ないことがあります)。この方法はディスクへのアクセス回数が少なくなるため、はるかに高速です。

例: ファイルのコピー

任意のバイナリファイル(例えば画像)を別の場所にコピーする簡単なプログラムを書きます。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 8 KB — 多くの用途で最適なサイズ
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("コピーが完了しました!");
        } catch (IOException e) {
            System.out.println("コピー時のエラー: " + e.getMessage());
        }
    }
}

とても簡単です。元ファイルからブロックで読み取り、そのまま新しいファイルへ書き込みます。この方法は画像、アーカイブ、動画など、あらゆるファイルで機能します。

3. バイナリファイルの書き込み

バイト配列の書き込み

バイト配列がある場合(例えばネットワークから受け取った、あるいはプログラムで生成したもの)、次のようにファイルへ書き込めます:

byte[] data = new byte[] {1, 2, 3, 4, 5}; // 配列の例

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // 配列全体をファイルに書き込みます
}

メソッド write(byte[]) は配列内のすべてのバイトを書き込みます。配列の一部だけを書き込むこともできます: out.write(data, offset, length)

ファイルを分割して書く(例: コピー時)

読み取り時と同様、通常はバッファを使います:

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

ここではファイルから読み取ったものを、そのまますぐに別のファイルへ書き込みます。この種のコードはアーカイバ、ダウンローダ、画像処理ツールなどでよく見られます。

4. 役に立つポイント

なぜ Reader/Writer をバイナリファイルに使ってはいけないのか?

ReaderWriter は(char)という文字を扱い、バイトではありません。これらは(例えば UTF-8 などの)エンコーディングに従ってバイトを文字に自動変換します。テキストには便利ですが、バイナリファイルに対しては致命的です!

FileWriter で画像を書こうとすると、開けない壊れたファイルになります。覚えておいてください: 非テキストのファイルには必ず InputStream/OutputStream とそのサブクラスだけを使いましょう

バイナリファイル操作の重要な違いと注意点

  • バッファサイズ: バッファが小さすぎると(ディスクアクセスが増えるため)遅くなり、大きすぎると余分なメモリを消費します。416 KB が一般的に最適です。
  • エラー処理: 常に IOException を処理してください。ファイルが存在しない、ロックされている、ディスク容量が尽きるといったことがあり得ます。
  • ストリームのクローズ: try-with-resources を使えば、エラーが起きても確実にファイルを閉じられます。
  • 上書きについて: new FileOutputStream("file.bin") で開くと上書きされます。末尾に追記するには、append = true のコンストラクタを使用してください。
  • アクセス権: プログラムがファイルを開けない場合、読み取り/書き込みの権限を確認しましょう。
  • readAllBytes(): ファイル全体を 1 行でバイト配列に読み込めます。大きなファイルには使わないでください。メモリを使い果たしてしまいます!

5. バイナリファイル操作のよくあるミス

エラー No.1: バイナリファイルに FileReader/FileWriter を使う。 これらのクラスはバイトを文字へ、またはその逆へ変換するため、画像やアーカイブなどに対してはデータ破損を招きます。

エラー No.2: read() の戻り値を無視する。 メソッド read(byte[]) は、要求したサイズより少ないバイト数しか読めないことがあります(特に最後のブロック)。実際に処理すべきバイト数を知るため、必ず戻り値を使いましょう。

エラー No.3: ストリームを閉じ忘れる。 ファイルを閉じないとロックされたままになったり、(特に書き込み時に)データが最後まで書き出されないことがあります。try-with-resources を使いましょう。

エラー No.4: サイズを考慮せずにファイル全体をメモリへ読み込もうとする。 大きなファイルでは OutOfMemoryError を引き起こします。バッファを使い、分割して読み取りましょう。

エラー No.5: 例外を処理しない。 ファイル操作では常にエラーが起こり得ます。ファイルが見つからない、権限がない、ディスクが満杯など。IOException の処理を忘れないでください。

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