BinaryReader/Writerクラス
ストリームでバイナリを手軽に扱うクラス
Streamクラスを利用したデータの読み書きは、データをbyte型やbyte型の配列で扱います。
そのままではやや扱いづらいので、BinaryReader、BinaryWriterクラスを利用した読み書き方法が提供されています。
BinaryReader/WriterクラスはStream派生クラスと組み合わせて使用します。
まずStream派生クラスのインスタンスを生成し、それをBinaryReader/Writerのコンストラクターに渡します。
これでBinaryReader/WriterはStreamとの間に入ってデータを適切に加工してくれます。
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
//bwを通してmsに書き込み
bw.Write(123);
bw.Close();
ms.Close(); //必要ない
BinaryReader/Writerクラスを閉じたとき、操作対象のストリームも同時に閉じるのでClose
(Dispose
)メソッドを二回呼ぶ必要はありません。
BinaryReader/Writerを閉じても元のストリームを閉じないように動作を変更することも可能です。
このページではusingステートメントを利用したコードで記述します。
using (MemoryStream ms = new MemoryStream())
using (BinaryReader br = new BinaryReader(ms))
{
//読み書き処理...
}
//この時点でmsとbrはクローズ、破棄されている
共通の操作
コンストラクター
BinaryReader/Writerクラスのコンストラクターは、他の(Stream派生クラスの)ストリームのインスタンスを指定します。
他にふたつのオーバーロードがあります。
以下はBinaryReaderクラスを例に説明しますが、BinaryWriterクラスも同様のオーバーロードがあります。
オーバーロード | 説明 |
---|---|
BinaryReader(Stream input) | 指定のストリームをBinaryReaderで読み取る。 エンコーディングはUTF-8を使用する。 |
BinaryReader(Stream input, Encoding encoding) | 指定のストリームをBinaryReaderで読み取る。 文字エンコーディングはencodingを使用する。 |
BinaryReader(Stream input, Encoding encoding, bool leaveOpen) | 指定のストリームをBinaryReaderで読み取る。 エンコーディングはencodingを使用する。 leaveOpenにtrueを指定すると、BinaryReaderのインスタンスのクローズ後に元のストリームを開いたままにする。 (.NET Framework4.5以降) |
文字エンコーディング
BinaryReader/Writerクラスは、文字や文字列の読み書きの際に標準でUTF-8を使用します。
文字コードを変更する場合はコンストラクターでEncodingクラスを指定します。
(System.Text
名前空間)
//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クラスを参照してください。
BaseStreamプロパティ
BinaryReader/WriterのインスタンスはBaseStream
プロパティで操作対象のストリームを取得することができます。
using (MemoryStream ms = new MemoryStream())
using (BinaryReader br = new BinaryReader(ms))
{
//BaseStreamプロパティはMemoryStreamを返すので
//MemoryStreamのメソッドを使用可能
br.BaseStream.WriteByte((byte)1);
}
ただし、同じ操作がBinaryReader/Writerクラスで提供されている場合、そちらを通して操作を行うことが推奨されます。
BaseStreamプロパティからベースとなるストリームの操作を行った場合、BinaryReader/Writer側の操作は行われないので、必要な処理が行われない可能性があります。
ベースとなるストリームのインスタンス(上記コードの例では変数ms
)を直接操作することも同様なので注意してください。
シーク
シーク操作はBinaryWriterクラスにはSeek
メソッドがあるので、これを使用します。
BinaryReaderクラスには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;
}
BinaryWriterクラス
Writeメソッド
データの書き込みには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型配列を書き込むこともできます。
戻り値はありません。
Write7BitEncodedIntメソッド
Write7BitEncodedInt
メソッドは、int型(32bit整数)の数値を圧縮形式で書き込みます。
Write7BitEncodedInt64
メソッドは、long型(64bit整数)の数値を圧縮形式で書き込みます。
using (MemoryStream ms = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(ms))
{
bw.Write7BitEncodedInt(10);
bw.Write7BitEncodedInt(100);
bw.Write7BitEncodedInt(1000);
bw.Flush();
bw.Seek(0, SeekOrigin.Begin);
int b = 0;
while ((b = ms.ReadByte()) >= 0) {
Console.Write("{0} ", b);
}
}
10 100 232 7
int型は通常であればその値に関係なく4バイト必要ですが、このメソッドは値が小さい場合にはサイズを縮小して書き込める可能性があります。
逆に、大きな値を書き込んだ場合は5バイトを必要とする場合もあります。
また、負数は常に最大のバイトを必要とするようです。
(Write7BitEncodedInt
なら5バイト、Write7BitEncodedInt64
なら9バイト)
このメソッドで書き込んだ数値は、必ずBinaryReaderクラスのRead7BitEncodedInt
(Read7BitEncodedInt64
)メソッドで読み取る必要があります。
ちなみに値とバイト数の関係は以下のようになります。
(「2^7」は2の7乗の意味)
値 | バイト数 |
---|---|
-1以下 | 5(32bit整数) 9(64bit整数) |
0~127(2^7 - 1) | 1 |
128~16,383(2^14 - 1) | 2 |
16,384~2,097,151(2^21 - 1) | 3 |
2,097,152~268,435,455(2^28 - 1) | 4 |
268,435,456~34,359,738,367(2^35 - 1) (int.MaxValue=2,147,483,647) |
5 |
34,359,738,368~ 4,398,046,511,103(2^42 - 1) |
6 |
4,398,046,511,104~ 562,949,953,421,311(2^49 - 1) |
7 |
562,949,953,421,312~ 72,057,594,037,927,935(2^56 - 1) |
8 |
72,057,594,037,927,936~ 9,223,372,036,854,775,807(long.MaxValue) |
9 |
メソッド名にもある通り、このメソッドはデータを7bit単位で区切って表現します。
残りの1bitは「全てのデータが変換されたか否か」を表すフラグに使用します。
7bitに収まる範囲の値は7bitデータに変換し、フラグには「0」がセットされるので1バイトのサイズで表現できます。
7bitに収まらない場合はフラグには「1」をセットし、次の7bitが使用されます。
これを全てのデータが格納されるまで繰り返します。
Flushメソッド
Flush
メソッドはBinaryWriter内のバッファを書き出してストリームに反映させます。
using (FileStream fs = new FileStream("test.bin", FileMode.Create))
using (BinaryWriter bw = new BinaryWriter(fs))
{
bw.Write(123);
bw.Write("abc");
//FileStreamにデータを反映
bw.Flush();
}
//BinaryWriterをクローズすることでも
//元ストリームに反映される
Flush
メソッドはベースとなるストリームのFlush
メソッドも呼び出すので、上記コードはファイルへの書き込みが行われます。
Flush
メソッドはStreamクラスで抽象メソッドとして定義されているので、すべてのStream派生クラスで使用することができます。
BinaryReaderクラス
Read○○系メソッド
データの読み込みにはBinaryReaderクラスのRead○○
系のメソッドを使用します。
ここでは先ほどのBinaryWriterクラスのWriteメソッドの説明で作成したファイル「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、ReadCharsメソッド
その他、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を返す |
Read7BitEncodedIntメソッド
Read7BitEncodedInt
メソッドは、圧縮形式で書き込まれたint型(32bit整数)を読み取ります。
Read7BitEncodedInt64
メソッドは、圧縮形式で書き込まれたlong型(64bit整数)を読み取ります。
これらはWrite7BitEncodedInt
(Write7BitEncodedInt64
)メソッドで書き込んだデータを読み取るためのものです。
通常のint型(long型)のデータは読み取れません。
読み取られるバイト数は値によって1~5バイトの範囲で変化します。
(Read7BitEncodedInt64
メソッドの場合は1~9バイト)
using (MemoryStream ms = new MemoryStream())
{
//BinaryWriterを閉じてもMemoryStreamは開いたままにする
using (BinaryWriter bw = new BinaryWriter(ms, Encoding.UTF8, true))
{
bw.Write7BitEncodedInt(10);
bw.Write7BitEncodedInt(100);
bw.Write7BitEncodedInt(1000);
}
ms.Position = 0;
using (BinaryReader br = new BinaryReader(ms))
{
int a = br.Read7BitEncodedInt();
Console.WriteLine(
"value: {0}, Position: {1}",
a, br.BaseStream.Position);
int b = br.Read7BitEncodedInt();
Console.WriteLine(
"value: {0}, Position: {1}",
b, br.BaseStream.Position);
int c = br.Read7BitEncodedInt();
Console.WriteLine(
"value: {0}, Position: {1}",
c, br.BaseStream.Position);
}
}
value: 10, Position: 1 value: 100, Position: 2 value: 1000, Position: 4
「10」と「100」は1バイト、「1000」は2バイトのサイズであることが分かります。