BinaryReaderクラスおよびBinaryWriterクラスはStreamに対してバイナリデータの読み書きを行うためのクラスです。 Stream単体ではバイト配列での読み書きしか行えませんが、StreamとBinaryReader・BinaryWriterを組み合わせて使うことで構造化されたバイナリデータの読み書きが可能になります。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイルsample.datを書き込み用に開く
using (Stream stream = File.OpenWrite("sample.dat")) {
// streamに書き込むためのBinaryWriterを作成
using (BinaryWriter writer = new BinaryWriter(stream)) {
// intの数値を書き込む
writer.Write((int)42);
// 4バイトのバイト配列を書き込む
writer.Write(new byte[] {0x01, 0x23, 0x45, 0x67});
}
}
// ファイルsample.datを読み込み用に開く
using (Stream stream = File.OpenRead("sample.dat")) {
// streamから読み込むためのBinaryReaderを作成
using (BinaryReader reader = new BinaryReader(stream)) {
// intの数値を読み込む
Console.WriteLine(reader.ReadInt32());
// 4バイト読み込む
byte[] data = reader.ReadBytes(4);
Console.WriteLine(BitConverter.ToString(data));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイルsample.datを書き込み用に開く
Using stream As Stream = File.OpenWrite("sample.dat")
' streamに書き込むためのBinaryWriterを作成
Using writer As New BinaryWriter(stream)
' Integerの数値を書き込む
writer.Write(CInt(42))
' 4バイトのバイト配列を書き込む
writer.Write(New Byte(3) {&h01, &h23, &h45, &h67})
End Using
End Using
' ファイルsample.datを読み込み用に開く
Using stream As Stream = File.OpenRead("sample.dat")
' streamから読み込むためのBinaryReaderを作成
Using reader As New BinaryReader(stream)
' intの数値を読み込む
Console.WriteLine(reader.ReadInt32())
' 4バイト読み込む
Dim data() As Byte = reader.ReadBytes(4)
Console.WriteLine(BitConverter.ToString(data))
End Using
End Using
End Sub
End Class
BinaryReader・BinaryWriterでは、読み書きの際のバイトオーダにリトルエンディアンを使用します。 実行環境のバイトオーダによらず、常にリトルエンディアンでの読み書きが行われます。
なお、.NET Frameworkにはビッグエンディアンで読み書きを行うBinaryReader・BinaryWriterは用意されていません。 BinaryReader・BinaryWriterが使用するバイトオーダを指定したり変更することもできません。 ビッグエンディアンでの読み書きを行うには、バイトオーダの変換などを自前で実装する必要があります。 この点については、ランタイム・システム・プラットフォームの情報 §.エンディアンや基本型の型変換 §.基本型とバイト配列への/からの変換などを参照してください。
BinaryReader
基本型の読み込み (ReadInt32, etc)
BinaryReaderには、基本型の読み込みを行うメソッドがいくつか用意されています。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// 整数型の値の読み込み
int i = reader.ReadInt32();
uint ui = reader.ReadUInt32();
byte by = reader.ReadByte();
// 実数型の値の読み込み
float f = reader.ReadSingle();
double d = reader.ReadDouble();
// bool型の値の読み込み
bool b = reader.ReadBoolean();
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' 整数型の値の読み込み
Dim i As Integer = reader.ReadInt32()
Dim ui As UInteger = reader.ReadUInt32()
Dim by As Byte = reader.ReadByte()
' 実数型の値の読み込み
Dim s As Single = reader.ReadSingle()
Dim d As Double = reader.ReadDouble()
' Boolean型の値の読み込み
Dim b As Boolean = reader.ReadBoolean()
End Using
End Using
End Sub
End Class
BinaryReaderに用意されているメソッドと、読み込める基本型は次のとおりです。 ほとんどの.NET Frameworkのプリミティブ型に対応する読み込みメソッドが用意されています。
メソッド | 読み込めるデータ型 | 読み込まれるデータのサイズ |
---|---|---|
ReadSByte | sbyte, SByte | 1バイト |
ReadInt16 | short, Short | 2バイト |
ReadInt32 | int, Integer | 4バイト |
ReadInt64 | long, Long | 8バイト |
ReadByte | byte, Byte | 1バイト |
ReadUInt16 | ushort, UShort | 2バイト |
ReadUInt32 | uint, UInteger | 4バイト |
ReadUInt64 | ulong, ULong | 8バイト |
ReadSingle | float, Single | 4バイト |
ReadDouble | double, Double | 8バイト |
ReadDecimal | decimal, Decimal | 16バイト |
ReadChar | char, Char | 2バイト |
ReadBoolean | bool, Boolean | 4バイト |
メソッド | 読み込めるデータ型 | 読み込まれるデータのサイズ |
すでに述べた通り、これらのメソッドで複数バイトのデータを読み込む場合、リトルエンディアンで読み込まれます。
BinaryReaderにはビット単位での読み込みを行うメソッドは用意されていません。
BinaryReaderにはDateTimeやDateTimeOffsetを読み込むメソッドも用意されていません。 DateTime・DateTimeOffsetの読み書きを行う場合はDateTime.ToBinary・DateTime.FromBinaryなどのメソッドを使って数値として扱う必要があります。
これらのメソッドでストリームの残りバイト数よりも多いバイト数を読み込もうとした場合(読み込むことによってストリームの末尾を超えてしまう場合)には、例外EndOfStreamExceptionがスローされます。 この際、読み込みに失敗してもストリームの現在位置は読み込む前の位置には戻りません。
using System;
using System.IO;
class Sample {
static void Main()
{
// 全6バイトのデータ
byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xab};
using (Stream stream = new MemoryStream(data)) {
using (BinaryReader reader = new BinaryReader(stream)) {
// int(4バイト)を読み込む
int i = reader.ReadInt32();
// ストリームの現在位置を表示
Console.WriteLine(reader.BaseStream.Position);
try {
// さらにもう一度int(4バイト)を読み込もうとする
// (実際に4バイト読み込むとストリームの末尾を超えてしまうため、例外EndOfStreamExceptionがスローされる)
i = reader.ReadInt32();
}
catch (EndOfStreamException) {
Console.WriteLine("end of stream");
}
// ストリームの現在位置を表示
Console.WriteLine(reader.BaseStream.Position);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' 全6バイトのデータ
Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hAB}
Using stream As Stream = New MemoryStream(data)
Using reader As New BinaryReader(stream)
' Integer(4バイト)を読み込む
Dim i As Integer = reader.ReadInt32()
' ストリームの現在位置を表示
Console.WriteLine(reader.BaseStream.Position)
Try
' さらにもう一度Integer(4バイト)を読み込もうとする
' (実際に4バイト読み込むとストリームの末尾を超えてしまうため、例外EndOfStreamExceptionがスローされる)
i = reader.ReadInt32()
Catch ex As EndOfStreamException
Console.WriteLine("end of stream")
End Try
' ストリームの現在位置を表示
Console.WriteLine(reader.BaseStream.Position)
End Using
End Using
End Sub
End Class
4 end of stream 6
バイト配列の読み込み (ReadBytes, Read)
ストリームから複数バイトを読み込みバイト配列として取得するにはReadBytesメソッドを使うことができます。 このメソッドでは、読み込みたいバイト数を引数に指定します。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// ストリームから16バイト読み込みバイト配列として取得する
byte[] data = reader.ReadBytes(16);
// 読み込んだバイト配列の内容を表示する
Console.WriteLine(BitConverter.ToString(data));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' ストリームから16バイト読み込みバイト配列として取得する
Dim data() As Byte = reader.ReadBytes(16)
' 読み込んだバイト配列の内容を表示する
Console.WriteLine(BitConverter.ToString(data))
End Using
End Using
End Sub
End Class
上記の例で使用しているBitConverter.ToStringメソッドはバイト配列を見やすい形式に変換するためのもので、ReadBytesメソッドを使った読み込み処理の本質とは無関係のものです。
ReadBytesメソッドでは、ストリームの残りバイト数よりも多いバイト数を読み込もうとした場合には実際に読み込めた分のみが返されます。 読み込み中にストリームの末尾に達した場合でもEndOfStreamExceptionはスローされません。 実際に読み込めたバイト数は返されるバイト配列の長さを調べることで知ることができます。 例えば、16バイト読み込もうとして実際には残り8バイトだった場合には、長さ8のバイト配列が返されます。 ストリームの末尾に達してそれ以上読み込めるデータがない場合、ReadBytesメソッドは長さ0のバイト配列を返します。
もうひとつ、バイト配列に読み込むメソッドとしてReadメソッドも用意されています。 こちらはReadBytesメソッドとは異なり、あらかじめ用意したバイト配列を指定することにより、読み込んだデータをその配列へ格納します。 Readメソッドは、Stream.Readメソッドと同様に指定したバイト数の分だけ読み込みを試みますが、指定したバイト数ちょうどのデータが一度に読み込まれるとは限りません。 読み込み中にストリームの末尾に達した場合でもEndOfStreamExceptionはスローされません。 実際に読み込めたバイト数は戻り値で知ることができます。 ストリームの末尾に達していてそれ以上読み込めるデータがない場合、Readメソッドは0を返します。
using System;
using System.IO;
class Sample {
static void Main()
{
// 8バイトのデータ
byte[] data = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
using (Stream stream = new MemoryStream(data)) {
using (BinaryReader reader = new BinaryReader(stream)) {
// 読み込んだデータを格納するためのバッファ
byte[] buffer = new byte[16];
// ストリームから最大6バイトを読み込み、buffer[0]以降に格納する
int len = reader.Read(buffer, 0, 6);
// 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len));
// ストリームから最大6バイトを読み込み、buffer[0]以降に格納する
len = reader.Read(buffer, 0, 6);
// 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' 8バイトのデータ
Dim data() As Byte = New Byte() {&h01, &h23, &h45, &h67, &h89, &hab, &hcd, &hef}
Using stream As Stream = New MemoryStream(data)
Using reader As New BinaryReader(stream)
' 読み込んだデータを格納するためのバッファ
Dim buffer(15) As Byte
' ストリームから最大6バイトを読み込み、buffer(0)以降に格納する
Dim len As Integer = reader.Read(buffer, 0, 6)
' 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len))
' ストリームから最大6バイトを読み込み、buffer(0)以降に格納する
len = reader.Read(buffer, 0, 6)
' 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len))
End Using
End Using
End Sub
End Class
6 01-23-45-67-89-AB 2 CD-EF
BinaryReaderにはStreamReader.ReadToEndメソッドのようなストリームの末尾までをひとつのバイト配列に読み込むメソッドは用意されていないため、必要な場合は自前で実装する必要があります。 次の例は、そのようなメソッドを実装した例です。 効率的な実装ではないため、あくまで参考程度のものです。
using System;
using System.Collections.Generic;
using System.IO;
class Sample {
// ストリームの末尾までを読み込みバイト配列として返すメソッド
private static byte[] ReadToEnd(BinaryReader reader)
{
// ストリームの長さの取得を試みる
long length;
try {
length = reader.BaseStream.Length;
}
catch (NotSupportedException) {
// 長さの取得をサポートしていないストリームの場合
length = -1;
}
if (length == -1) {
// 長さを取得できない場合は、Readメソッドを使ってストリームの末尾まで読み込む
// 一度のReadで読み込めたバイト配列を格納するリスト
List<ArraySegment<byte>> buffers = new List<ArraySegment<byte>>();
length = 0;
for (;;) {
byte[] buffer = new byte[1024];
int len = reader.Read(buffer, 0, buffer.Length);
if (len == 0)
// ストリームの末尾に達した
break;
// 読み込めたデータを含むバイト配列をArraySegmentに格納してリストに追加
buffers.Add(new ArraySegment<byte>(buffer, 0, len));
length += len;
}
// すべてのArraySegmentを連結し、ひとつのバイト配列にして返す
byte[] bytes = new byte[length];
int index = 0;
foreach (ArraySegment<byte> buffer in buffers) {
Buffer.BlockCopy(buffer.Array, buffer.Offset, bytes, index, buffer.Count);
index += buffer.Count;
}
return bytes;
}
else {
// 長さを取得できた場合は、ReadBytesメソッドを使ってストリームの末尾まで読み込む
return reader.ReadBytes((int)(length - reader.BaseStream.Position));
}
}
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
byte[] bytes = ReadToEnd(reader);
Console.WriteLine(BitConverter.ToString(bytes));
}
}
}
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Class Sample
' ストリームの末尾までを読み込みバイト配列として返すメソッド
Private Shared Function ReadToEnd(ByVal reader As BinaryReader) As Byte()
' ストリームの長さの取得を試みる
Dim length As Long
Try
length = reader.BaseStream.Length
Catch ex As NotSupportedException
' 長さの取得をサポートしていないストリームの場合
length = -1
End Try
If length = -1 Then
' 長さを取得できない場合は、Readメソッドを使ってストリームの末尾まで読み込む
' 一度のReadで読み込めたバイト配列を格納するリスト
Dim buffers As New List(Of ArraySegment(Of Byte))()
length = 0
Do
Dim buffer(1024 - 1) As Byte
Dim len As Integer = reader.Read(buffer, 0, buffer.Length)
If len = 0 Then
' ストリームの末尾に達した
Exit Do
End If
' 読み込めたデータを含むバイト配列をArraySegmentに格納してリストに追加
buffers.Add(New ArraySegment(Of Byte)(buffer, 0, len))
length += len
Loop
' すべてのArraySegmentを連結し、ひとつのバイト配列にして返す
Dim bytes(CInt(length) - 1) As Byte
Dim index As Integer = 0
For Each buffer As ArraySegment(Of Byte) In buffers
System.Buffer.BlockCopy(buffer.Array, buffer.Offset, bytes, index, buffer.Count)
index += buffer.Count
Next
return bytes
Else
' 長さを取得できた場合は、ReadBytesメソッドを使ってストリームの末尾まで読み込む
Return reader.ReadBytes(CInt(length - reader.BaseStream.Position))
End If
End Function
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
Dim bytes() As Byte = ReadToEnd(reader)
Console.WriteLine(BitConverter.ToString(bytes))
End Using
End Using
End Sub
End Class
上記のサンプル中で使用しているArraySegmentについては部分配列 §.ArraySegment構造体、Buffer.BlockCopyメソッドについてはバイト列操作 §.BlockCopyメソッドを参照してください。
なお、読み込み元がファイルに限定される場合はFileクラスのメソッドを使うこともできます。 File.ReadAllBytesメソッドを使うと、BinaryReaderを使わずにファイルの内容をバイト配列として読み込むことができます。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイルsample.datを読み込み、バイト配列として取得する
byte[] bytes = File.ReadAllBytes("sample.dat");
Console.WriteLine(BitConverter.ToString(bytes));
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイルsample.datを読み込み、バイト配列として取得する
Dim bytes() As Byte = File.ReadAllBytes("sample.dat")
Console.WriteLine(BitConverter.ToString(bytes))
End Sub
End Class
文字列の読み込み (ReadString)
ストリームから文字列を読み込むにはReadStringメソッドを使うことができます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// ストリームから文字列を読み込み表示する
string s = reader.ReadString();
Console.WriteLine(s);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' ストリームから文字列を読み込み表示する
Dim s As String = reader.ReadString()
Console.WriteLine(s)
End Using
End Using
End Sub
End Class
ReadStringメソッドは、BinaryWriter.Writeメソッドで書き込まれる形式での読み込みを行います。 BinaryWrite.Writeメソッドで文字列を書き込む場合、先頭に文字列の長さが書き込まれます。 ReadStringメソッドはその長さを元に文字列の読み込みを行います。
従ってReadStringメソッドでは、読み込む文字列の長さを事前に知っている必要はなく、引数で読み込む文字列の長さを指定する必要もありません。 逆に、ReadStringメソッドを固定長の文字列フィールドを読み込む目的に使用することはできません。 固定長の文字列フィールドの読み込みを行う場合は、ReadBytesメソッドやReadCharsメソッドを使って読み込み処理を記述する必要があります。 固定長の文字列フィールドを読み書きする具体例はBinaryWriter.Writeメソッドの解説をご覧ください。
ReadStringメソッドで文字列を読み込む際、デフォルトではUTF-8でデコードして読み込みますが、BinaryReaderのコンストラクタで目的のEncodingを指定することで任意のエンコーディングで文字列を読み込むことができます。 当然、BinaryWriter.Writeメソッドで書き込んだときと同じエンコーディングを指定しないと正しくデコードされません。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
// 文字列をデコードする際にShift_JISを使用するように指定してBinaryReaderを作成
using (BinaryReader reader = new BinaryReader(stream, Encoding.GetEncoding("Shift_JIS"))) {
// ストリームから文字列を読み込み表示する
string s = reader.ReadString();
Console.WriteLine(s);
}
}
}
}
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
' 文字列をデコードする際にShift_JISを使用するように指定してBinaryReaderを作成
Using reader As New BinaryReader(stream, Encoding.GetEncoding("Shift_JIS"))
' ストリームから文字列を読み込み表示する
Dim s As String = reader.ReadString()
Console.WriteLine(s)
End Using
End Using
End Sub
End Class
文字配列の読み込み (ReadChars)
ストリームから複数バイトを読み込み文字配列(char[])として取得するにはReadCharsメソッドを使うことができます。 このメソッドはReadBytesメソッドと似ていますが、引数にはバイト数ではなく読み込む文字数を指定します。 したがって、実際に読み込まれるバイト数はBinaryReaderが使用するエンコーディングによって変わります。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// ストリームから4文字読み込みchar配列として取得する
char[] chars = reader.ReadChars(4);
// 読み込んだchar配列を文字列に変換して表示
string s = new string(chars);
Console.WriteLine(s);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' ストリームから4文字読み込みchar配列として取得する
Dim chars() As Char = reader.ReadChars(4)
' 読み込んだchar配列を文字列に変換して表示
Dim s As New String(chars)
Console.WriteLine(s)
End Using
End Using
End Sub
End Class
ReadCharsメソッドを使うことでストリームから固定長の文字列フィールドを読み込むことができます。 固定長の文字列フィールドを読み書きする具体例はBinaryWriter.Writeメソッドの解説をご覧ください。
ReadStringメソッドの場合と同様、ReadCharメソッドはBinaryReaderのコンストラクタで指定したエンコーディングでのデコードを行います。 デフォルトではUTF-8でエンコードされているものとして読み込まれます。
文字単位での読み込み・先読み (Read, PeekChar)
Readメソッドを引数なしで呼び出すと、ストリームから1文字(char)を読み込みます。 ReadCharメソッドと似ていますが、戻り値はint/Integerで、ストリームの末尾に達した場合は-1が返されます。 そのため、ストリームの末尾に達した状態でこのメソッドを呼び出しても例外EndOfStreamExceptionはスローされません。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// 無限ループ
for (;;) {
// 一文字読み込む
int ch = reader.Read();
if (ch == -1)
// ストリームの末尾(EOF)に達したので読み込みを終了する
break;
// 読み込んだ値をcharにキャストして表示する
char c = (char)ch;
Console.Write(c);
}
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' 無限ループ
Do
' 一文字読み込む
Dim ch As Integer = reader.Read()
If ch = -1 Then
' ストリームの末尾(EOF)に達したので読み込みを終了する
Exit Do
End If
' 読み込んだ値をCharに変換して表示する
Dim c As Char = ChrW(ch)
Console.Write(c)
Loop
End Using
End Using
End Sub
End Class
ReadStringメソッドの場合と同様、ReadメソッドはBinaryReaderのコンストラクタで指定したエンコーディングでのデコードを行います。 デフォルトではUTF-8でエンコードされているものとして読み込まれます。
PeekCharメソッドを使うと、次の1文字を先読みすることができます。 Peekメソッドの戻り値は引数なしのReadメソッドと同様です。
シーク
BinaryReaderにはストリームのシークを行うメソッドは用意されていません。 シークを行うには、まずBaseStreamプロパティを参照してベースとなっているStreamを取得し、そして取得したStreamのSeekメソッドを呼び出すようにします。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// データブロックのサイズを読み込む
int blockSize = reader.ReadInt32();
// シークしてデータブロックを読み飛ばす
reader.BaseStream.Seek(blockSize, SeekOrigin.Current);
// 次のデータを読み込む
int data = reader.ReadInt32();
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' データブロックのサイズを読み込む
Dim blockSize As Integer = reader.ReadInt32()
' シークしてデータブロックを読み飛ばす
reader.BaseStream.Seek(blockSize, SeekOrigin.Current)
' 次のデータを読み込む
Dim data As Integer = reader.ReadInt32()
End Using
End Using
End Sub
End Class
BinaryReaderでは内部でデータの先読みとバッファリングが行われるようになっているため、BaseStreamを参照してストリームのシークを行うと読み込まれる内容に不整合が起こる可能性が考えられます。 実際にそういったことが起こるかどうかはドキュメントには明記されていないため不明確ですが、BinaryReaderにSeekメソッドが用意されていない点を勘案すると、BinaryReaderでランダムアクセスを行うのは避けたほうがよいと思われます。
データを読み飛ばす目的でシークを行いたい場合には、シークを行うかわりにReadBytes等のメソッドを使ってシークしたい分だけデータを読み込み、その戻り値は単に破棄する、といった方法をとることができます。 この方法の場合、ストリーム後方へのシークのみしかできないものの、操作によってバッファリングされている内容に不整合が起きることはないため、確実な方法と言えます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// データブロックのサイズを読み込む
int blockSize = reader.ReadInt32();
// データブロックを読み捨てる
reader.ReadBytes(blockSize);
// 次のデータを読み込む
int data = reader.ReadInt32();
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' データブロックのサイズを読み込む
DIm blockSize As Integer = reader.ReadInt32()
' データブロックを読み捨てる
reader.ReadBytes(blockSize)
' 次のデータを読み込む
Dim data As Integer = reader.ReadInt32()
End Using
End Using
End Sub
End Class
BinaryWriter
基本型の書き込み
BinaryWriterで書き込みを行う場合はWriteメソッドを使います。 このメソッドでは与えられた引数の型に従って内容が書き込まれます。 Writeメソッドで基本型の書き込みを行う際、数値リテラルを直接指定して書き込む場合は、リテラルにサフィックスをつけたり明示的にキャストすることにより、書き込もうとしている値の型を明確にすることをおすすめします。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenWrite("sample.dat")) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// 整数型の値の書き込み
writer.Write((int)16);
writer.Write((uint)42);
writer.Write((byte)72);
// 実数型の値の読み込み
writer.Write(0.05f); // float
writer.Write(Math.PI); // double
// bool型の値の読み込み
writer.Write(true);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
Using writer As New BinaryWriter(stream)
' 整数型の値の書き込み
writer.Write(CInt(16))
writer.Write(CUInt(42))
writer.Write(CByte(72))
' 実数型の値の読み込み
writer.Write(0.05f) ' Single
writer.Write(Math.PI) ' Double
' Boolean型の値の読み込み
writer.Write(True)
End Using
End Using
End Sub
End Class
Writeメソッドで書き込める基本型の種類はReadメソッドと同じです。 WriteメソッドではDateTimeやDateTimeOffsetを直接書き込むことはできないので、ToBinary/FromBinaryなどのメソッドを使って数値などに変換してから書き込む必要があります。
バイト配列の書き込み
Writeメソッドではバイト配列の書き込みにも対応しています。 引数に指定する値と意味はStream.Writeメソッドと同じです。 BinaryWriter.Writeメソッドでは、バイト配列のみを指定することでその内容すべてを書き込むようにすることもできます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenWrite("sample.dat")) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// 書き込むデータが格納されているバイト配列
byte[] data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// dataの0番目から4バイト分(buffer[0]〜buffer[3])を書き込む
writer.Write(data, 0, 4);
// dataの4番目から4バイト分(buffer[4]〜buffer[7])を書き込む
writer.Write(data, 4, 4);
// data[0]〜data[7]のすべてを書き込む
writer.Write(data);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
Using writer As New BinaryWriter(stream)
' 書き込むデータが格納されているバイト配列
Dim data() As Byte = New Byte(7) {&H41, &H42, &H43, &H44, &H45, &H46, &H47, &H48}
' dataの0番目から4バイト分(buffer(0)〜buffer(3))を書き込む
writer.Write(data, 0, 4)
' dataの4番目から4バイト分(buffer(4)〜buffer(7))を書き込む
writer.Write(data, 4, 4)
' data(0)〜data(7)のすべてを書き込む
writer.Write(data)
End Using
End Using
End Sub
End Class
なお、書き込み先がファイルに限定される場合はFileクラスのメソッドを使うこともできます。 File.WriteAllBytesメソッドを使うと、BinaryWriterを使わずにバイト配列をファイルに書き込むことができます。
using System;
using System.IO;
class Sample {
static void Main()
{
// 書き込むデータが格納されているバイト配列
byte[] data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// バイト配列の内容をファイルsample.datに書き込む
File.WriteAllBytes("sample.dat", data);
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' 書き込むデータが格納されているバイト配列
Dim data() As Byte = New Byte(7) {&H41, &H42, &H43, &H44, &H45, &H46, &H47, &H48}
' バイト配列の内容をファイルsample.datに書き込む
File.WriteAllBytes("sample.dat", data)
End Sub
End Class
文字列の書き込み
Writeメソッドは文字列の書き込みにも対応しています。
using System;
using System.IO;
class Sample {
static void Main()
{
using (MemoryStream stream = new MemoryStream()) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// Writeメソッドで文字列を書き込む
writer.Write("ABCDEFGH");
}
// 実際に書き込まれた内容を表示する
Console.WriteLine(BitConverter.ToString(stream.ToArray()));
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As new MemoryStream()
Using writer As New BinaryWriter(stream)
' Writeメソッドで文字列を書き込む
writer.Write("ABCDEFGH")
End Using
' 実際に書き込まれた内容を表示する
Console.WriteLine(BitConverter.ToString(stream.ToArray()))
End Using
End Sub
End Class
08-41-42-43-44-45-46-47-48
BinaryWriterで文字列を書き込む場合、まず文字列の長さがストリームに書き込まれ、続けて文字列のバイト表現が書き込まれます。 これにより、読み込みの際に文字列の具体的な長さを知らなくても任意の長さの文字列を読み込むことができるようになっています。 実際、文字列を読み込むBinaryReader.ReadStringメソッドには引数で読み込む文字列の長さを指定することはできません。
上記の実行結果における1バイト目の 0x08 は後続する文字列のバイト数が 8 であることを表しています。 BinaryReader.ReadStringメソッドのドキュメントによると、文字列とその長さは次のように書き込まれます。
現在のストリームから 1 つの文字列を読み取ります。ストリームの文字列は、7 ビットごとにエンコードされた文字列の長さが先頭に付加されています。
BinaryReader.ReadString メソッド ()
Writeメソッドで文字列を書き込む際、デフォルトではUTF-8にエンコードされて書き込まれますが、BinaryWriterのコンストラクタで目的のEncodingを指定することで任意のエンコーディングで文字列を書き込むことができます。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
using (Stream stream = File.OpenWrite("sample.dat")) {
// 文字列をエンコードする際にShift_JISを使用するように指定してBinaryWriterを作成
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.GetEncoding("Shift_JIS"))) {
// Writeメソッドで文字列を書き込む
writer.Write("あいう日本語");
}
}
}
}
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 文字列をエンコードする際にShift_JISを使用するように指定してBinaryWriterを作成
Using writer As New BinaryWriter(stream, Encoding.GetEncoding("Shift_JIS"))
' Writeメソッドで文字列を書き込む
writer.Write("あいう日本語")
End Using
End Using
End Sub
End Class
文字配列の書き込み
Writeメソッドに文字配列(char[])を指定して書き込みを行う場合は、文字列を書き込む場合とは異なり先頭に文字列の長さは書き込まれません。 そのため、ReadCharsメソッドと組み合わせて使うことで固定長の文字列フィールドの読み書きができるようになります。
以下の例では、ストリームの先頭に全フィールド数、続けて長さ8の固定長文字列フィールドを複数書き込み、それをBinaryReaderで読み込んでいます。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイルに固定長文字列データを複数書き込む
using (Stream stream = File.OpenWrite("sample.dat")) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// 書き込む文字列
string[] fields = new string[] {
"0123456789",
"あいう日本語",
"Hello, world!",
};
// 全フィールド数を書き込む
writer.Write(fields.Length);
// 長さ8の固定長文字列フィールドを書き込む
foreach (string field in fields) {
// 書き込む文字列の長さがちょうど8となるように加工する
string s = field;
if (8 < field.Length)
// 8より長い場合は、8文字に切り詰める
s = field.Substring(0, 8);
else if (field.Length < 8)
// 8より短い場合は、8文字になるまでヌル文字で埋める
s = field.PadRight(8, '\0');
// 文字列をchar[]に変換して書き込む
writer.Write(s.ToCharArray());
}
}
}
// ファイルから固定長文字列データを複数読み込む
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// 全フィールド数を読み込む
int fieldCount = reader.ReadInt32();
for (int i = 0; i < fieldCount; i++) {
// 長さ8の固定長文字列フィールドを読み込み、文字列に変換する
string s = new string(reader.ReadChars(8));
// ヌル文字で埋められている部分を削る
s = s.TrimEnd('\0');
// 得られた文字列を表示
Console.WriteLine("'{0}'", s);
}
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイルに固定長文字列データを複数書き込む
Using stream As Stream = File.OpenWrite("sample.dat")
Using writer As New BinaryWriter(stream)
' 書き込む文字列
Dim fields() As String = New String() { _
"0123456789", _
"あいう日本語", _
"Hello, world!" _
}
' 全フィールド数を書き込む
writer.Write(fields.Length)
' 長さ8の固定長文字列フィールドを書き込む
For Each field As String In fields
' 書き込む文字列の長さがちょうど8となるように加工する
Dim s As String = field
If 8 < field.Length Then
' 8より長い場合は、8文字に切り詰める
s = field.Substring(0, 8)
ElseIf field.Length < 8 Then
' 8より短い場合は、8文字になるまでヌル文字で埋める
s = field.PadRight(8, ChrW(0))
End If
' 文字列をChar()に変換して書き込む
writer.Write(s.ToCharArray())
Next
End Using
End Using
' ファイルから固定長文字列データを複数読み込む
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' 全フィールド数を読み込む
Dim fieldCount As Integer = reader.ReadInt32()
For i As Integer = 0 To fieldCount - 1
' 長さ8の固定長文字列フィールドを読み込み、文字列に変換する
Dim s As New String(reader.ReadChars(8))
' ヌル文字で埋められている部分を削る
s = s.TrimEnd(ChrW(0))
' 得られた文字列を表示
Console.WriteLine("'{0}'", s)
Next
End Using
End Using
End Sub
End Class
'01234567' 'あいう日本語' 'Hello, w'
文字列を書き込む場合と同様、文字配列を書き込む際にはBinaryWriterのコンストラクタで指定したエンコーディングでエンコードされます。 デフォルトではUTF-8でエンコードされた上で書き込まれます。
シーク
BinaryWriterの書き込み位置を変更するにはSeekメソッドを使うことができます。 また、BaseStreamプロパティを参照してベースとなっているStreamを取得し、そして取得したStreamのSeekメソッドを呼び出すことでもシークを行うことができます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (Stream stream = File.OpenWrite("sample.dat")) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// データを書き込む
writer.Write((int)16);
// ストリームの先頭にシーク
writer.Seek(0, SeekOrigin.Begin);
//writer.BaseStream.Seek(0, SeekOrigin.Begin); // このようにしても同じ
// 別のデータを上書きする
writer.Write((int)42);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
Using writer As New BinaryWriter(stream)
' データを書き込む
writer.Write(CInt(16))
' ストリームの先頭にシーク
writer.Seek(0, SeekOrigin.Begin)
'writer.BaseStream.Seek(0, SeekOrigin.Begin) ' このようにしても同じ
' 別のデータを上書きする
writer.Write(CInt(42))
End Using
End Using
End Sub
End Class
BinaryWriterは書き込み内容のバッファリングは行われないため、BinaryReaderのシークの場合とは異なりシークを行なっても書き込まれる内容に不整合が起こることはありません。
構造体・クラスの読み書き
BinaryReader・BinaryWriterには任意の構造体・クラスを読み書きするメソッドは用意されていません。 そのため、BinaryReader・BinaryWriterで構造体やクラスを扱う場合は、以下のようにフィールドをひとつずつ読み書きする必要があります。
using System;
using System.IO;
// ファイルへの読み書きで使用する構造体
struct Date {
public short Year;
public byte Month;
public byte Day;
}
class Sample {
static void Main()
{
using (Stream stream = File.OpenWrite("sample.dat")) {
using (BinaryWriter writer = new BinaryWriter(stream)) {
// 構造体を作成し、値を設定
Date d = new Date();
d.Year = 2013;
d.Month = 4;
d.Day = 1;
// 構造体のフィールドを1つずつ書き込む
writer.Write(d.Year);
writer.Write(d.Month);
writer.Write(d.Day);
}
}
using (Stream stream = File.OpenRead("sample.dat")) {
using (BinaryReader reader = new BinaryReader(stream)) {
// 構造体を作成し、値を読み込む
Date d = new Date();
d.Year = reader.ReadInt16();
d.Month = reader.ReadByte();
d.Day = reader.ReadByte();
// 構造体に読み込んだ内容を表示
Console.WriteLine("{0}-{1}-{2}", d.Year, d.Month, d.Day);
}
}
}
}
Imports System
Imports System.IO
' ファイルへの読み書きで使用する構造体
Structure MyDate
Public Year As Short
Public Month As Byte
Public Day As Byte
End Structure
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
Using writer As New BinaryWriter(stream)
' 構造体を作成し、値を設定
Dim d As New MyDate
d.Year = 2013
d.Month = 4
d.Day = 1
' 構造体のフィールドを1つずつ書き込む
writer.Write(d.Year)
writer.Write(d.Month)
writer.Write(d.Day)
End Using
End Using
Using stream As Stream = File.OpenRead("sample.dat")
Using reader As New BinaryReader(stream)
' 構造体を作成し、値を読み込む
Dim d As MyDate
d.Year = reader.ReadInt16()
d.Month = reader.ReadByte()
d.Day = reader.ReadByte()
' 構造体に読み込んだ内容を表示
Console.WriteLine("{0}-{1}-{2}", d.Year, d.Month, d.Day)
End Using
End Using
End Sub
End Class
ストリーム中のブロックを構造体として読み書きしたい場合、fread
/fwrite
関数のような読み書きを行いたい場合は、いったんバイト配列として読み書きし、さらにバイト配列から構造体に変換する必要があります。 具体的な実装例についてはBinaryReader・BinaryWriterでの構造体の読み書きで紹介しています。
ベースとなるストリームのクローズ
BinaryReader・BinaryWriterでは、Closeメソッドを呼び出したりusingステートメントから抜けた際にはベースとなったストリームも閉じられ、ストリームに対するアクセスができなくなります。 BinaryReader・BinaryWriterを閉じたあとにベースとなったストリームに対して操作を行おうとすると例外ObjectDisposedExceptionがスローされます。 これは、StreamReader・StreamWriterでも同様です。 BinaryReader・BinaryWriterを使用したあとも引き続きストリームを使う方法についてはStreamReader・StreamWriterの解説を参照してください。
.NET Framework 4.5からは、コンストラクタの引数leaveOpenにtrueを指定すると、BinaryReader・BinaryWriterを閉じてもベースとなるストリームを開いたままにすることができます。 ただし、同時に引数encodingも省略せずに指定する必要があります。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
using (Stream stream = File.OpenRead("sample.dat")) {
// leaveOpenにtrueを指定してBinaryReaderを作成
const bool leaveOpen = true;
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen)) {
reader.ReadByte();
}
// Usingステートメントから抜けてreaderが閉じられても
// streamは閉じられないのでObjectDisposedExceptionはスローされない
stream.ReadByte();
}
using (Stream stream = File.OpenWrite("sample.dat")) {
// leaveOpenにtrueを指定してBinaryWriterを作成
const bool leaveOpen = true;
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen)) {
writer.Write(16);
}
// BinaryWriterの場合も同様で、ObjectDisposedExceptionはスローされない
stream.WriteByte(0);
}
}
}
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
' leaveOpenにTrueを指定してBinaryReaderを作成
Const leaveOpen As Boolean = True
Using reader As New BinaryReader(stream, Encoding.UTF8, leaveOpen)
reader.ReadByte()
End Using
' usingステートメントから抜けてreaderが閉じられても
' streamは閉じられないのでObjectDisposedExceptionはスローされない
stream.ReadByte()
End Using
Using stream As Stream = File.OpenWrite("sample.dat")
' leaveOpenにTrueを指定してStreamWriterを作成
Dim leaveOpen As Boolean = True
Using writer As New BinaryWriter(stream, Encoding.UTF8, leaveOpen)
writer.Write(16)
End Using
' BinaryWriterの場合も同様で、ObjectDisposedExceptionはスローされない
stream.WriteByte(0)
End Using
End Sub
End Class