連載
» 2007年11月08日 05時00分 公開

.NET TIPS:バイナリ・ファイルを読み書きするには?[C#、VB]

バイナリ・ファイル(=テキスト・ファイル以外のファイル)を読み書きするには、FileStreamクラスとSeek/Read/Writeの各メソッドを利用する。C#およびVBでの使い方を解説する。

[遠藤孝信,デジタルアドバンテージ]
「.NET TIPS」のインデックス

連載目次

 バイナリ・ファイル(=テキスト・ファイル以外のファイル)を読み書きするには、FileStreamクラス(System.IO名前空間)を利用する。

 FileStreamクラスはファイル用のストリームをサポートするクラスであり、Streamクラス(System.IO名前空間)の派生クラスの1つだ*1。基本的には、コンストラクタで指定したファイルのストリームに対して、Seekメソッドにより読み書きを行う位置決めを行い、Readメソッド/Writeメソッドで読み書きを行う。

*1 Streamクラスの派生クラスには、ほかに、

  • NetworkStreamクラス(System.Net.Sockets名前空間)
  • MemoryStreamクラス(System.IO名前空間)
  • BufferedStreamクラス(System.IO名前空間)
  • GZipStreamクラス(System.IO.Compression名前空間)
  • CryptoStreamクラス(System.Security.Cryptography名前空間)

などがある。


ReadメソッドとWriteメソッド

 FileStreamクラスのReadメソッドでは、パラメータとして、読み込んだデータを格納するためのbyte配列、読み込んだデータを書き込む(配列内の)位置、読み込むバイト数を指定する。

 ファイルのどの位置のデータを読み込むかはReadメソッドでは指定しない。読み込みの開始位置はFileStreamオブジェクトによりストリームの「現在位置」として管理されており、これはReadメソッドを呼び出すたびに、読み込んだ分だけ先に進む。つまり、繰り返しReadメソッドを呼び出すことにより、順次データを読み込んでいけるというわけだ。

 ファイルの任意の位置のデータを読み込むには、あらかじめSeekメソッドによりストリームの現在位置を移動させる。Seekメソッドの第1パラメータでは、現在位置を示すオフセットをlong型の値で指定する。第2パラメータでは、それが先頭あるいは末尾からのオフセットであるか、現在位置からのオフセットであるかをSeekOrigin列挙体(System.IO名前空間)の値により指定する。

 Readメソッドの戻り値は、実際に読み込んだバイト数となる。ファイルの末尾付近では、この値は第3パラメータで指定した値(読み込むバイト数)よりも小さくなる可能性がある。また、ストリームの現在位置がファイルの末尾に達している場合には、Readメソッドの戻り値は0となる。

 次のコードは、バイナリ・ファイル「test.bin」をオープンし、byte配列に1024bytesずつ読み込むサンプル・プログラムである。FileStreamクラスでは、Lengthプロパティによりオープンしているファイルのサイズを取得できる。

// binread.cs

using System;
using System.IO;

class BinaryRead {
  static void Main(string[] args) {

    FileStream fs = new FileStream(
      @"test.bin", FileMode.Open, FileAccess.Read);

    int fileSize = (int)fs.Length; // ファイルのサイズ
    byte[] buf = new byte[fileSize]; // データ格納用配列

    int readSize; // Readメソッドで読み込んだバイト数
    int remain = fileSize; // 読み込むべき残りのバイト数
    int bufPos = 0; // データ格納用配列内の追加位置

    while (remain > 0) {

      // 1024Bytesずつ読み込む
      readSize = fs.Read(buf, bufPos, Math.Min(1024, remain));

      bufPos += readSize;
      remain -= readSize;
    }
    fs.Dispose();
  }
}

// コンパイル方法:csc binread.cs

Readメソッドによりバイナリ・ファイルを読み込むC#のサンプル・プログラム(binread.cs)
binread.csのダウンロード

' binread.vb

Imports System
Imports System.IO

Class BinaryRead
  Shared Sub Main(ByVal args As String())
    Dim fs As New FileStream( _
      "test.bin", FileMode.Open, FileAccess.Read)

    Dim fileSize As Integer = CInt(fs.Length) ' ファイルのサイズ
    Dim buf(fileSize - 1) As Byte ' データ格納用配列

    Dim readSize As Integer ' Readメソッドで読み込んだバイト数
    Dim remain As Integer = fileSize ' 読み込むべき残りのバイト数
    Dim bufPos As Integer = 0 ' データ格納用配列内の追加位置

    While remain > 0
      ' 1024Bytesずつ読み込む
      readSize = fs.Read(buf, bufPos, Math.Min(1024, remain))

      bufPos += readSize
      remain -= readSize
    End While
  End Sub
End Class

' コンパイル方法:vbc binread.vb

Readメソッドによりバイナリ・ファイルを読み込むVBのサンプル・プログラム(binread.vb)
binread.vbのダウンロード

 ここでは記述例を示すために1024bytesずつファイルを読み込んでいるが、(読み込むファイルのサイズが巨大でなければ)次のようにして全体を一度に読み込んでもよい。

readSize = fs.Read(buf, 0, fs.Length)



 一方、バイナリ・データ(byte配列)をファイルに書き込むには、Writeメソッドを使用する。Writeメソッドのパラメータには、データが格納されているbyte配列、書き込むデータの配列内での位置、書き込むバイト数を指定する。Readメソッドと同様に、書き込む位置はストリームの現在位置となる(Writeメソッドと上述したSeekメソッドのコード例については、次のサンプル・プログラムを参照)。

読み込みエラーを無視してコピーを行うサンプル・プログラム

 次のコードは、Read、Write、Seekメソッドを使用したサンプル・プログラムである。このプログラムでは、読み込み時のエラーを無視してコピーを行う(読み取れなかった部分は0で埋める)。

 傷の付いたDVDなどを読もうとしたら、CRCエラーが出てしまったが、一部欠損していてもよいので、とにかくファイルをコピーしたいといった場合に利用できるプログラムだ。

// bincopy.cs

using System;
using System.IO;

class BinaryCopy {
  static void Main(string[] args) {

    string srcName = args[0];  // コピー元のファイル名
    string destName = args[1]; // コピー先のファイル名

    const int BUFSIZE = 2048; // 1度に処理するサイズ
    byte[] buf = new byte[BUFSIZE]; // 読み込み用バッファ
    byte[] ZEROARRAY = new byte[BUFSIZE]; // 0埋め用

    int readSize; // Readメソッドで読み込んだバイト数

    using (FileStream src = new FileStream(
        srcName, FileMode.Open, FileAccess.Read))
    using (FileStream dest = new FileStream(
        destName, FileMode.Create, FileAccess.Write)) {

      while (true) {
        try {
          readSize = src.Read(buf, 0, BUFSIZE); // 読み込み
        } catch {
          // 読み込みに失敗した場合
          Console.WriteLine("read error at " + src.Position);

          if (src.Length - src.Position < BUFSIZE) {
            readSize = (int)(src.Length - src.Position);
          } else {
            readSize = BUFSIZE;
          }
          src.Seek(readSize, SeekOrigin.Current);
          dest.Write(ZEROARRAY, 0, readSize); // 0埋めで書き込み
          continue;
        }
        if (readSize == 0) {
          break; // コピー完了
        }
        dest.Write(buf, 0, readSize); // 書き込み
      }
    }
  }
}

// コンパイル方法:csc bincopy.cs

読み込みエラーを無視してコピーを行うC#のサンプル・プログラム(bincopy.cs)
bincopy.csのダウンロード

' bincopy.vb

Imports System
Imports System.IO

Class BinaryCopy
  Shared Sub main(ByVal args As String())

    Dim srcName As String = args(0) ' コピー元のファイル名
    Dim destName As String = args(1) ' コピー先のファイル名

    Dim BUFSIZE As Integer = 2048 ' 1度に処理するサイズ
    Dim buf(BUFSIZE) As Byte ' 読み込み用バッファ
    Dim ZEROARRAY(BUFSIZE) As Byte ' 0埋め用

    Dim readSize As Integer ' Readメソッドで読み込んだバイト数

    Using src As New FileStream( _
        srcName, FileMode.Open, FileAccess.Read)
      Using dest As New FileStream( _
          destName, FileMode.Create, FileAccess.Write)

        While True
          Try
            readSize = src.Read(buf, 0, BUFSIZE) ' 読み込み
          Catch
            ' 読み込みに失敗した場合
            Console.WriteLine("read error at " & src.Position)

            If src.Length - src.Position < BUFSIZE Then
              readSize = CLng(src.Length - src.Position)
            Else
              readSize = BUFSIZE
            End If

            src.Seek(readSize, SeekOrigin.Current)
            dest.Write(ZEROARRAY, 0, readSize) ' 0埋めで書き込み

            Continue While
          End Try

          If readSize = 0 Then
            Exit While ' コピー完了
          End If

          dest.Write(buf, 0, readSize) ' 書き込み

        End While
      End Using
    End Using
  End Sub
End Class

' コンパイル方法:vbc bincopy.vb

読み込みエラーを無視してコピーを行うVBのサンプル・プログラム(bincopy.vb)
bincopy.vbのダウンロード

 プログラムでは、Readメソッドが失敗したときには(この場合には例外が発生する)、読み込み用のストリームをSeekメソッドにより進め、書き込み用のストリームには0を書き込む。FileStreamクラスのPositionプロパティはストリームの現在位置を表す。

 なお、SeekメソッドはFileStreamオブジェクトが内部で管理する現在位置を移動するだけで、ファイルの読み込めない部分に現在位置を移動したとしても呼び出しは失敗しない。実際にエラーが発生するのは、その後にReadメソッドを呼び出した場合である。

利用可能バージョン:.NET Framework 2.0のみ
カテゴリ:クラス・ライブラリ 処理対象:バイナリ・ファイル
使用ライブラリ:FileStreamクラス(System.IO名前空間)
使用ライブラリ:SeekOrigin列挙体(System.IO名前空間)


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。