【C#】バイナリファイルを少しずつ読み込む (FileStreamとyield return)

画像ファイルや実行ファイル(DLL/EXE)などのバイナリデータを扱う際、File.ReadAllBytes で一括でメモリに読み込むと、巨大なファイルを扱った際にメモリ不足(OutOfMemory)になるリスクがあります。

FileStreamyield return を組み合わせて、決まったサイズ(バッファサイズ)ごとに分割して読み込むことで、メモリ消費を抑えながら安全に処理する方法を解説します。

目次

実装サンプル:バイナリデータの分割読み込み

以下のコードでは、ファイルを 1KB (1024バイト) ずつ読み込み、その都度コンソールにサイズを出力しています。

サンプルコード

C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class Program
{
    public static void Main()
    {
        // 読み込み対象のファイルパス
        string filePath = "test_data.bin";

        // 1. テスト用に適当なバイナリファイルを作成 (2500バイト)
        if (!File.Exists(filePath))
        {
            byte[] dummyData = new byte[2500];
            new Random().NextBytes(dummyData); // ランダムな値で埋める
            File.WriteAllBytes(filePath, dummyData);
        }

        Console.WriteLine($"ファイルサイズ: {new FileInfo(filePath).Length} バイト\n");

        // 2. FileStreamを開く
        using (FileStream fs = File.OpenRead(filePath))
        {
            // 3. 自作メソッドを使って 1024バイトずつ取り出す
            int count = 1;
            foreach (byte[] chunk in ReadBinaryFile(fs, 1024))
            {
                Console.WriteLine($"[{count}回目] 読み込みデータ長: {chunk.Length} bytes");
                // ここで chunk に対して必要なバイナリ解析処理を行う
                
                count++;
            }
        }
    }

    // ストリームから指定サイズずつ読み出して返すメソッド (イテレータ)
    static IEnumerable<byte[]> ReadBinaryFile(Stream stream, int bufferSize)
    {
        // 読み込み用バッファ
        byte[] buffer = new byte[bufferSize];
        int readSize;

        // stream.Read は実際に読めたバイト数を返す (0なら終了)
        while ((readSize = stream.Read(buffer, 0, buffer.Length)) > 0)
        {
            // バッファがいっぱいまで読めた場合と、最後の方で端数しか読めない場合がある
            if (readSize == bufferSize)
            {
                // バッファをそのまま返す(コピーして返すと安全だが、今回は簡略化のためコピー推奨)
                // yield return buffer; // 参照渡しになるため注意が必要
                yield return buffer.ToArray(); 
            }
            else
            {
                // 実際の読み込みサイズに合わせて切り出して返す
                yield return buffer.Take(readSize).ToArray();
            }
        }
    }
}

実行結果

ファイルサイズ: 2500 バイト

[1回目] 読み込みデータ長: 1024 bytes
[2回目] 読み込みデータ長: 1024 bytes
[3回目] 読み込みデータ長: 452 bytes

解説と技術的なポイント

1. FileStream.Read

バイナリ読み込みの基本メソッドです。 stream.Read(buffer, offset, count) の形式で呼び出します。 戻り値として「実際に読み込めたバイト数」が返ってきます。これが 0 の場合、ファイルの終端(EOF)に達したことを意味します。

2. yield return の活用

巨大なファイルを扱う場合、すべての分割データを List<byte[]> に入れてから返すと、結局メモリを大量消費してしまいます。 yield return を使うことで、呼び出し元(Mainメソッドのforeach)がデータを必要としたタイミングで、1ブロックずつ読み込みと処理が行われるため、メモリ効率が非常に良くなります。

3. Take(readSize).ToArray()

読み込みループの最後(ファイルの末尾)では、バッファサイズ(今回は1024)いっぱいまでデータがないことがあります(実行結果の452バイトの部分)。 そのため、buffer をそのまま返すのではなく、有効なデータが入っている部分だけを切り出して返す処理が必要です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次