BinaryReader/Writerクラス

Streamクラスとバイナリ

Streamクラスを利用したデータの読み書きは、データをbyte型の配列(バイナリ)で扱います。
そのままではやや扱いづらいので、BinaryReaderBinaryWriterクラスを利用した読み書き方法が提供されています。

BinaryReader/WriterクラスはStream(の派生)クラスと組み合わせて使用します。
まずStreamのインスタンスを生成し、それをBinaryReader/Writerのコンストラクターに渡します。
これでBinaryReader/WriterはStreamとの間に入ってデータを適切に加工してくれます。


MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);

//brを通してmsの読み書き
bw.Write(123);

bw.Close();
ms.Close();

このページではusingステートメントを利用したコードで記述します。


using (MemoryStream ms = new MemoryStream())
using (BinaryReader br = new BinaryReader(ms))
{
    //読み書き処理...
}

BaseStreamプロパティ

BinaryReader/WriterのインスタンスはBaseStreamプロパティで操作対象のストリームを取得することができます。


using (MemoryStream ms = new MemoryStream())
using (BinaryReader br = new BinaryReader(ms))
{
    //BaseStreamプロパティはMemoryStreamを返すので
    //MemoryStreamのメソッドを使用可能
    br.BaseStream.WriteByte((byte)1);
}

BinaryWriterクラス

データの書き込みにはBinaryWriterクラスのWriteメソッドを使用します。


string path = "test.bin";

byte[] bytes = new byte[] { 1, 2, 3, 4, 5 };
char[] chars = new char[] { 'a', 'b', 'c', 'd', 'e' };

using (FileStream fs = new FileStream(path, FileMode.Create))
using (BinaryWriter bw = new BinaryWriter(fs))
{
    bw.Write(123);  //int型
    bw.Write(4.56); //double型
    bw.Write(true); //bool型 
    bw.Write("あいうえお"); //string型

    //byte型配列をすべて書き込み
    bw.Write(bytes);

    //配列の2番目の要素から3つ分を書き込み
    bw.Write(bytes, 2, 3);

    //char型配列をすべて書き込み
    bw.Write(chars);

    //配列の2番目の要素から3つ分を書き込み
    bw.Write(chars, 2, 3);
}

Writeメソッドはobject型以外の組み込み型をそのまま書き込むことができます。
その他、byte型配列とchar型配列を書き込むこともできます。

Encoding

文字や文字列を書き込む際、文字コードはデフォルトでUTF-8で書き込まれます。
文字コードを変更する場合はコンストラクターでEncodingを指定します。
(System.Text.Encoding)


//UTF-16
using (var ms = new MemoryStream())
using (var bw = new BinaryWriter(ms, Encoding.Unicode))
{
}

//Shift-JIS
using (var ms = new MemoryStream())
using (var bw = new BinaryWriter(ms, Encoding.GetEncoding("shift-jis")))
{
}

詳しくはEncodingクラスを参照してください。

シーク

シークを行うにはSeekメソッドを使用するほか、BaseStreamを経由してSeekメソッドを実行あるいはPositionプロパティを操作します。


using (MemoryStream ms = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(ms))
{
    for (int i = 0; i < 255; i++)
        bw.Write((byte)i);

    bw.Seek(0, SeekOrigin.Begin);

    bw.BaseStream.Seek(0, SeekOrigin.End);
    bw.BaseStream.Position = 0;
}

BinaryReaderクラス

データの読み込みにはBinaryReaderクラスを使用します。
ここでは先ほど作成したファイル「test.bin」を読み込んでみます。


string path = "test.bin";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (BinaryReader br = new BinaryReader(fs))
{
    Console.WriteLine(br.ReadInt32());
    Console.WriteLine(br.ReadDouble());
    Console.WriteLine(br.ReadBoolean());
    Console.WriteLine(br.ReadString());

    foreach (var b in br.ReadBytes(5))
        Console.Write("{0} ", b);
    Console.WriteLine();

    foreach (var b in br.ReadBytes(3))
        Console.Write("{0} ", b);
    Console.WriteLine();

    foreach (var c in br.ReadChars(5))
        Console.Write("{0} ", c);
    Console.WriteLine();

    foreach (var c in br.ReadChars(3))
        Console.Write("{0} ", c);
    Console.WriteLine();
}
123
4.56
True
あいうえお
1 2 3 4 5 
3 4 5 
a b c d e 
c d e 

BinaryReaderの読み取りメソッドには以下があります。

メソッド名 読み取るサイズ 戻り値の型
ReadByte 1バイト byte型
ReadSByte 1バイト sbyte型
ReadInt16 2バイト short型
ReadUInt16 2バイト ushort型
ReadInt32 4バイト int型
ReadUInt32 4バイト uint型
ReadInt64 8バイト long型
ReadUInt64 8バイト ulong型
ReadSingle 4バイト float型
ReadDouble 8バイト double型
ReadDecimal 16バイト decimal型
ReadBoolean 4バイト bool型
ReadChar 1文字 char型
ReadString 任意 string型

これらのメソッドはストリームの現在の位置から指定のバイト数分を読み取り、指定のデータ型に変換します。
現在の位置に期待通りのデータが存在しなければ正しいデータは得られません。

ReadCharメソッドは「1バイト」ではなく「1文字」を読み取ります。
1文字のバイト数は文字コードによって変わります。

ReadStringメソッドは文字列を読み取りますが、読み取れるのはBinaryWriterを利用して書き込んだ文字列に限ります。
BinaryWriterの文字列書き込みはデータの先頭に文字列の長さを格納しており、BinaryReaderはこれを利用して読み取る文字列の長さを判断しています。
BinaryWriterを経由せずに文字列を書き込んだ場合は長さが格納されていないため、ReadStringメソッドでは読み取ることはできません。

上記の表のメソッドは、ストリームの終端を超えてデータを読み取ろうとすると例外(EndOfStreamException)が発生します。

その他、ReadBytesメソッドでbyte型配列を、ReadCharsメソッドでchar型配列を読み取れます。
ReadBytesメソッドの引数は読み取るバイト数を指定します。
ReadCharsメソッドの引数は読み取る文字数を指定します。

この二つのメソッドはストリームの末尾を超えてデータを読み取ろうとするとストリームの残りのデータの配列が返されます。
例えばストリームの残りが5バイトのとき、「br.ReadBytes(10)」を実行すると要素数5のbyte型配列が返されます。
ストリームが終端の場合は要素数0の配列が返されます。
つまり例外は発生しません。

Read、PeekChar

データ読み取りメソッドにはReadメソッドとPeekCharメソッドも使用できます。

メソッド 説明 戻り値 ストリーム終端を超えた場合
Read() 1文字を読み取る
読み取ったデータが現在の文字列エンコードに変換できない場合は例外発生(ArgumentException)
読み取った文字(int型) -1を返す
Read(byte[] buffer, int index, int count) countバイトを読み取りbuffer[index]から順に格納 読み取れたバイト数(int型) 残りのデータをbufferに格納し、読み取れたバイト数を返す
すでに終端に位置する場合は0を返す
Read(char[] buffer, int index, int count) count文字分を読み取りbuffer[index]から順に格納 読み取れた文字数(int型) 残りのデータをbufferに格納し、読み取れた文字数を返す
すでに終端に位置する場合は0を返す
PeekChar() 1文字を読み取るが、ストリームの位置は移動させない
読み取ったデータが現在の文字列エンコードに変換できない場合は例外発生(ArgumentException)
読み取った文字(int型) -1を返す

Encoding

文字や文字列を読み取る際、文字コードはデフォルトでUTF-8で変換されます。
文字コードを変更する場合はコンストラクターでEncodingを指定します。
これはBinaryWriterのEncodingの設定と同じです。
(System.Text.Encoding)


//UTF-16
using (var ms = new MemoryStream())
using (var br = new BinaryReader(ms, Encoding.Unicode))
{
}

//Shift-JIS
using (var ms = new MemoryStream())
using (var br = new BinaryReader(ms, Encoding.GetEncoding("shift-jis")))
{
}

読み込みと書き込みのEncodingは同じにする必要があります。

シーク

BinaryWriterにはSeekメソッドがありますが、BinaryReaderにはありません。
BinaryReaderでシークをする場合はBaseStreamを経由してシークします。
当然ながらストリームがシーク可能な場合に限ります。


using (MemoryStream ms = new MemoryStream())
using (BinaryReader br = new BinaryReader(ms))
{
	if(br.BaseStream.CanSeek)
        br.BaseStream.Position = 0;
}