ここでは.NET Frameworkにおいてストリームを扱うクラスであるStreamクラスと、Streamクラスを使ったデータの読み書きといった操作について見ていきます。
ストリームとは
データストリーム、もしくは単にストリームとは、一言で言えばひとつながりのデータ列のことを表します。 データにはその実体が存在する場所や、データが流れてくる経路によって様々なものがあります。 例えば、ディスク上のファイルから読み込まれるデータ、メモリ上から読み取られるデータ、ネットワークを経由して転送されてくるデータ、プロセスの標準入出力を経由して渡されるデータなどがありますが、そういった何らかの経路を経て流れてくるデータ列を総称してストリームと呼びます。
ストリームにおいて重要なのは、データがどのような経路から流れてくるのかといった点を抽象化し、特に流れてくるデータだけに着目できるようにするという点です。 経路や実体を抽象化して単なるデータ列と捉えることにより、様々なデータに対する読み込み・書き込みの操作を画一的な方法で扱えるようになります。
.NET FrameworkにおけるストリームとStreamクラス
.NET Frameworkでは、ストリームを扱うためのクラスとしてStreamクラスが用意されています。 Streamクラスは単にストリームを表すだけでなく、ストリームに対する読み込み・書き込み・シークといった操作が用意されています。
Streamクラスを使ったストリームの読み書きとユーティリティクラス
Streamクラスはすべてのストリームの基本となる抽象型で、より具体的なストリームはこのクラスから継承して実装されます。 例えば、ファイルを開いて読み書きするためのクラスにFileStreamクラスがありますが、これはStreamクラスから派生したものです。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイルsample.datを開き、読み取りアクセスを行うためのFileStreamを作成する
using (var stream = new FileStream("sample.dat", FileMode.Open, FileAccess.Read)) {
// 読み込んだデータを格納するためのバッファ
var buffer = new byte[4];
// FileStreamから4バイト読み込みバッファに格納する
stream.Read(buffer, 0, 4);
// 読み込んだデータをInt32(int)に変換して表示する
var number = BitConverter.ToInt32(buffer, 0);
Console.WriteLine(number);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイルsample.datを開き、読み取りアクセスを行うためのFileStreamを作成する
Using stream As New FileStream("sample.dat", FileMode.Open, FileAccess.Read)
' 読み込んだデータを格納するためのバッファ
Dim buffer(3) As Byte
' FileStreamから4バイト読み込みバッファに格納する
stream.Read(buffer, 0, 4)
' 読み込んだデータをInt32(int)に変換して表示する
Dim number As Integer = BitConverter.ToInt32(buffer, 0)
Console.WriteLine(number)
End Using
End Sub
End Class
上記の例のようにStreamクラスに用意されているメソッドを使うことによってデータの読み書きができます。 ただ、Streamクラス単体ではバイト単位・バイナリレベルでの読み書きしかできないので、例えば数値を扱うにはBitConverterクラスを使って読み書きの度に型変換を行うといったことをする必要があります。
しかし、それはStreamクラスを単体で使う場合に限ったことで、.NET FrameworkにはStreamクラスを使った数値・文字列の読み書きを簡単に行えるようにする便利なユーティリティクラスが用意されています。 具体的には、StreamReader・StreamWriterやBinaryReader・BinaryWriterといったクラスをStreamクラスと組み合わせて使うことにより、数値・文字列など構造化されたデータをより簡単に読み書きできるようになります。
using System;
using System.IO;
class Sample {
static void Main()
{
// ファイルsample.datを開き、読み取りアクセスを行うためのFileStreamを作成する
using (var stream = new FileStream("sample.dat", FileMode.Open, FileAccess.Read)) {
// streamからデータを読み出すBinaryReaderを作成する
var reader = new BinaryReader(stream);
// streamからInt32(int)のデータを読み込み、表示する
var number = reader.ReadInt32();
Console.WriteLine(number);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' ファイルsample.datを開き、読み取りアクセスを行うためのFileStreamを作成する
Using stream As New FileStream("sample.dat", FileMode.Open, FileAccess.Read)
' streamからデータを読み出すBinaryReaderを作成する
Dim reader As New BinaryReader(stream)
' streamからInt32(Integer)のデータを読み込み、表示する
Dim number As Integer = reader.ReadInt32()
Console.WriteLine(number)
End Using
End Sub
End Class
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
// ファイルsample.txtを開き、読み取りアクセスを行うためのFileStreamを作成する
using (var stream = new FileStream("sample.txt", FileMode.Open, FileAccess.Read)) {
// テキストエンコーディングにUTF-8を用いてstreamの読み込みを行うStreamReaderを作成する
var reader = new StreamReader(stream, Encoding.UTF8);
// streamから文字列を一行分読み込み、表示する
var line = reader.ReadLine();
Console.WriteLine(line);
}
}
}
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
' ファイルsample.txtを開き、読み取りアクセスを行うためのFileStreamを作成する
Using stream As New FileStream("sample.txt", FileMode.Open, FileAccess.Read)
' テキストエンコーディングにUTF-8を用いてstreamの読み込みを行うStreamReaderを作成する
Dim reader As New StreamReader(stream, Encoding.UTF8)
' streamから文字列を一行分読み込み、表示する
Dim line As String = reader.ReadLine()
Console.WriteLine(line)
End Using
End Sub
End Class
ここまでの例で挙げたFileStreamはファイルに対する読み書きを行うためのストリームですが、他にもStreamの一種としてバイト配列をストリームとして扱い読み書きを行うためのMemoryStreamが用意されています。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
// 何らかのデータが格納されているバイト配列を想定
var data = new byte[32];
// バイト配列を読み取り専用のストリームとして扱うMemoryStreamを作成する
using (var stream = new MemoryStream(data, false)) {
// テキストエンコーディングにUTF-8を用いてstreamの読み込みを行うStreamReaderを作成
var reader = new StreamReader(stream, Encoding.UTF8);
// streamから文字列を一行分読み込み、表示する
var line = reader.ReadLine();
Console.WriteLine(line);
}
}
}
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
' 何らかのデータが格納されているバイト配列を想定
Dim data(31) As Byte
' バイト配列を読み取り専用のストリームとして扱うMemoryStreamを作成する
Using stream As New MemoryStream(data, false)
' テキストエンコーディングにUTF-8を用いてstreamの読み込みを行うStreamReaderを作成
Dim reader As New StreamReader(stream, Encoding.UTF8)
' streamから文字列を一行分読み込み、表示する
Dim line As String = reader.ReadLine()
Console.WriteLine(line)
End Using
End Sub
End Class
このように、入力ソースがFileStreamであってもMemoryStreamであっても、実際に読み込みを行う部分のコードはどちらも同じです。 StreamReader・StreamWriterやBinaryReader・BinaryWriterといったユーティリティクラスでは、データがファイルとして存在するのか、バイト配列として存在するのかといった違いがあっても、それらがStreamである限りはいずれも同じように扱うことができます。
StreamReader・StreamWriterやBinaryReader・BinaryWriter以外にもStreamを使った読み取り・書き込みをサポートしているクラスは多数あります。 これらのクラスでは、Streamクラスをサポートすることによりファイルやメモリ以外の入力ソースにも幅広く柔軟に対応できるようになっています。

Streamの種類
データソースを抽象化するStream派生クラス
FileStreamやMemoryStream以外にも、.NET Frameworkには各種データソースに対応したStream派生クラスが用意されています。 以下のクラスは、そのようなStream派生クラスの一例です。
クラス | 概要 |
---|---|
FileStream | ファイルシステム上のファイルへの読み書きを行うためのStream (詳細) |
MemoryStream | メモリ上に確保されているバイト配列への読み書きを行うためのStream (詳細) |
UnmanagedMemoryStream | アンマネージブロック上に確保されている領域への読み書きを行うためのStream (詳細) |
NetworkStream | ソケットを使ってデータの送受信を行うためのStream |
データソースの種類によっては具体的なStream派生クラスが公開されていないものも存在しますが、そういったものでも何らかのメソッド呼び出しを利用することでStreamが取得できるものもあります。 Streamを取得するメソッドには次のようなものがあります。
クラス | 概要 |
---|---|
Console.OpenStandardInput
Console.OpenStandardOutput Console.OpenStandardError |
自プロセスの標準ストリームを開いてStreamを取得するためのメソッド |
Assembly.GetManifestResourceStream | アセンブリに埋め込まれたリソースを開いてStreamを取得するためのメソッド |
File.Create
File.Open File.OpenRead File.OpenWrite |
ファイルを開いてFileStreamを取得するためのメソッド (FileStreamのコンストラクタ呼び出しをメソッド形式にして簡略化したもの) |
もちろん、Streamを継承して独自にストリームを実装したり、機能を拡張することも可能です。
データフォーマットの変換やバッファリングなどの機能を追加するStream派生クラス
FileStreamやMemoryStreamといったクラスは各種データソースからの読み書きを実装・抽象化したものですが、Stream派生クラスの一部には他のStreamのラッパーとして動作するものが存在します。 このようなStream派生クラスでは、データソースとなるStreamをラップし、Streamからの読み書きに際してエンコード/デコードや暗号化/復号化などといったデータフォーマットの変換を行うものや、読み書きするデータのバッファリングを行うなどの機能を追加します。 ラッパーとなるStreamでは、データソースに対する読み書き自体は下位のStreamに行わせ、自身は読み書きする側とデータソースの間に入り追加の処理を行います。
例えば次のコードでは、CryptoStreamを使ってデータをBASE64形式にエンコードした上でFileStreamに書き込んでいます。
using System;
using System.IO;
using System.Security.Cryptography;
class Sample {
static void Main()
{
// ファイルsample.datを開き、書き込みアクセスを行うためのFileStreamを作成する
using (var fileStream = new FileStream("sample.dat", FileMode.Create, FileAccess.Write)) {
// BASE64への変換を行った上でfileStreamに書き込みを行うCryptoStreamを作成する
using (var base64Stream = new CryptoStream(fileStream, new ToBase64Transform(), CryptoStreamMode.Write)) {
// 書き込むデータが格納されているバイト配列
var data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// データを書き込む
// CryptoStreamによりデータはBASE64にエンコードされ、次いで
// FileStreamによりエンコードされたデータがファイルに書き込まれる
base64Stream.Write(data, 0, 8);
}
}
}
}
Imports System
Imports System.IO
Imports System.Security.Cryptography
Class Sample
Shared Sub Main()
' ファイルsample.datを開き、書き込みアクセスを行うためのFileStreamを作成する
Using fileStream As Stream = New FileStream("sample.dat", FileMode.Create, FileAccess.Write)
' BASE64への変換を行った上でfileStreamに書き込みを行うCryptoStreamを作成する
Using base64Stream As Stream = New CryptoStream(fileStream, New ToBase64Transform(), CryptoStreamMode.Write)
' 書き込むデータが格納されているバイト配列
Dim data() As Byte = New Byte() {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' データを書き込む
' CryptoStreamによりデータはBASE64にエンコードされ、次いで
' FileStreamによりエンコードされたデータがファイルに書き込まれる
base64Stream.Write(data, 0, 8)
End Using
End Using
End Sub
End Class
QUJDREVGR0g=
このように、CryptoStreamを使うことで他のStreamに対してBASE64エンコードを行う機能を追加することができます。
CryptoStreamはBASE64形式でのエンコード・デコード以外にも、様々な形式での暗号化やフォーマット変換を行うために使われます。
上記のコードにおける書き込むデータ・CryptoStreamとFileStream・書き込まれるファイルの関係を図式化すると次のようになります。
[データ data]
↓
CryptoStream
(BASE64へエンコードする)
↓
FileStream
(ファイルへ書き込む)
↓
[ファイル sample.dat]
逆に、読み込みを行う場合の関係は次のようになります。
[データ data]
↑
CryptoStream
(BASE64からデコードする)
↑
FileStream
(ファイルから読み込む)
↑
[ファイル sample.dat]
CryptoStream以外にも、Streamの読み書きに何らかの機能を追加するラッパーとして動作するStream派生クラスには次のようなものがあります。
クラス | 概要 |
---|---|
BufferedStream | Streamにバッファリングを行う機能を追加するためのStream |
GZipStream
DeflateStream |
Streamにgzip形式・Deflate形式での圧縮・展開機能を追加するためのStream (使用例・解説) |
AuthenticatedStream
SslStream |
StreamにSSLなどのセキュリティ・プロトコルの署名・暗号化機能を追加するためのStream |
CryptoStream | Streamに各種暗号化やフォーマット変換を行う機能を追加するためのStream (使用例) |
これらのクラスは、多段に重ねて使うことも可能です。 例えば、NetworkStreamをSslStreamでラップすることで通信にTLS/SSLを用いるようにし、さらにGZipStreamでラップすることでデータをgzip形式で圧縮してから送信する、といったことができます。
[データ]
↓
GZipStream
(gzip形式で圧縮する)
↓
SslStream
(TLS/SSLによる暗号化を行う)
↓
NetworkStream
(ソケットを使って送信する)
↓
[ネットワーク]
このように、Streamクラスは単純なデータの読み書きからフォーマット変換などの高度な機能の追加まで、様々な目的・方法で汎用的に使用することが出来ます。
Streamクラス
ここからはStreamクラスの基本的な使い方について解説します。 Streamクラスには読み書きを行うメソッドが用意されていますが、これらを直接使って読み書きすることはまれで、ほとんどの場合はStreamReader・StreamWriterやBinaryReader・BinaryWriterで事足ります。
以下の解説中にあるサンプルコードでは、具体例を提示する都合上FileStreamを使っている箇所が多くありますが、特に注記している場合を除いてすべてのStreamに共通する事柄を述べています。 例えば、FileStreamを使っている箇所をMemoryStreamなどに置き換えた場合でも同じように動作します。
読み込み操作
Streamクラスを使ってデータの読み込みを行う方法について。
読み込み (Read, ReadByte)
Streamからデータを読み込むには、Readメソッドを使います。 Readメソッドの引数・戻り値は次のとおりです。
- 第1引数 buffer
- Streamから読み込んだデータを格納するためのバイト配列
- 第2引数 offset
- Streamから読み込んだデータを格納する際の、格納先となるバイト配列中の開始インデックス
- 第3引数 count
- Streamから読み込むデータの最大バイト数
- 戻り値
- 実際にStreamから読み込めたデータのバイト数
つまり、Readメソッドの戻り値をlenとすると、ReadメソッドでStreamから読み込んだデータはbuffer[offset]からbuffer[offset + len - 1]の範囲に格納されることになります。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenRead("sample.dat")) {
// 読み込んだデータを格納するためのバッファ
var buffer = new byte[8];
// streamから最大4バイトを読み込み、buffer[0]以降に格納する
var len = stream.Read(buffer, 0, 4);
// 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len));
// streamから最大2バイトを読み込み、buffer[4]以降に格納する
len = stream.Read(buffer, 4, 2);
// 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 4, len));
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
' 読み込んだデータを格納するためのバッファ
Dim buffer(7) As Byte
' streamから最大4バイトを読み込み、buffer(0)以降に格納する
Dim len As Integer = stream.Read(buffer, 0, 4)
' 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 0, len))
' streamから最大2バイトを読み込み、buffer(4)以降に格納する
len = stream.Read(buffer, 4, 2)
' 実際に読み込めた分を表示する
Console.WriteLine("{0} {1}", len, BitConverter.ToString(buffer, 4, len))
End Using
End Sub
End Class
上記の例で使用しているBitConverter.ToStringメソッドはバイト配列を見やすい形式に変換するためのもので、Readメソッドを使った読み込み処理の本質とは無関係のものです
Readメソッドでは、指定したバイト数の分だけ読み込みを試みますが、指定したバイト数ちょうどのデータが一度に読み込まれるとは限りません。 例えば、読み込んだ結果ストリームの終端に達した場合や、NetworkStreamにおいてデータが送受信の途中だった場合などは、Readメソッドが読み込むバイト数は指定したバイト数よりも少なくなります。
Readメソッドで実際に読み込むことができたデータの長さは、戻り値によって知ることができます。 Streamから読み込めるデータが無くなった場合(すでにストリームの終端に達している場合)は、戻り値として0が返されます。 Readメソッドでの読み込みが成功した場合、読み込めたバイト数だけストリームの現在位置(Position)が移動します。
次の例は、用意したバッファがいっぱいになるまでReadメソッドで読み込みを行う例です。 このコードでは用意したバッファがいっぱいになるか、ストリームの終端に達するまで読み込みを続けます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenRead("sample.dat")) {
var buffer = new byte[16]; // 読み込んだデータを格納するためのバッファ
var offset = 0; // 読み込んだデータを格納する位置の初期値
var count = buffer.Length; // 読み込むバイト数の初期値
for (;;) {
// 最大countバイト読み込み、bufferのインデックスoffset以降に格納する
int len = stream.Read(buffer, offset, count);
if (len == 0) {
break; // これ以上読み込めるデータが無いので、読み込みを終了する
}
else {
offset += len; // 読み込めた長さの分だけ、次回のReadでバッファに格納する位置を移動する
count -= len; // 読み込めた長さの分だけ、次回のReadで読み込むバイト数を減らす
}
}
// 実際に読み込めた分を表示する
Console.WriteLine(BitConverter.ToString(buffer, 0, offset));
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Dim buffer(15) As Byte ' 読み込んだデータを格納するためのバッファ
Dim offset As Integer = 0 ' 読み込んだデータを格納する位置の初期値
Dim count As Integer = buffer.Length ' 読み込むバイト数の初期値
Do
' 最大countバイト読み込み、bufferのインデックスoffset以降に格納する
Dim len As Integer = stream.Read(buffer, offset, count)
If len = 0 Then
Exit Do ' これ以上読み込めるデータが無いので、読み込みを終了する
Else
offset += len ' 読み込めた長さの分だけ、次回のReadでバッファに格納する位置を移動する
count -= len ' 読み込めた長さの分だけ、次回のReadで読み込むバイト数を減らす
End If
Loop
' 実際に読み込めた分を表示する
Console.WriteLine(BitConverter.ToString(buffer, 0, offset))
End Using
End Sub
End Class
ReadByteメソッドを使うと、Streamからデータを1バイトずつ読み込むことができます。 このメソッドでは、読み込めたデータは戻り値として返されます。 ただし、型はint/Integerとなっているため、読み込めたデータはbyte/Byteにキャストして使います。 ReadByteメソッドの呼び出したとき既にストリームの終端に達していている場合は-1が返されます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenRead("sample.dat")) {
for (;;) {
// streamから1バイト読み込む
int data = stream.ReadByte();
if (data == -1)
// 終端に達した
break;
// byteにキャストして表示
byte b = (byte)data;
Console.Write("{0:x2} ", b);
}
Console.WriteLine();
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
Do
' streamから1バイト読み込む
Dim data As Integer = stream.ReadByte()
If data = -1 Then Exit Do ' 終端に達した
' byteにキャストして表示
Dim b As Byte = CByte(data)
Console.Write("{0:x2} ", b)
Loop
Console.WriteLine()
End Using
End Sub
End Class
Streamクラス自体はバッファリングを行わないため、Peekなどのような先読みを行うメソッド・プロパティは用意されていません。 先読みを行いたい場合はBinaryReader.PeekCharメソッドあるいはStreamReader.Peekメソッドを使います。 バッファリングを行いたい場合はストリームをBufferedStreamクラスでラップします。 Stream派生クラスでは、FileStreamクラスのようにクラスの機能としてバッファリングを行うように実装されている場合もあります。
現在位置と長さ (Position, Length)
ストリーム内の現在位置を取得するにはPositionプロパティを参照します。 この値は、Readメソッド・Writeメソッドで読み書きを行う際の開始位置となります。 読み書きの両方が可能なストリームでも、読み込みと書き込みの開始位置は常に同じとなる(別々ではない)点に注意してください。
また、ストリームの長さを取得するにはLengthプロパティを参照します。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenRead("sample.dat")) {
// streamの長さと現在の位置を表示
Console.WriteLine("Length : {0}", stream.Length);
Console.WriteLine("Position : {0}", stream.Position);
// streamからデータを読み込む
var buffer = new byte[8];
var len = stream.Read(buffer, 0, buffer.Length);
// 読み込めたバイト数と現在の位置を表示
Console.WriteLine("Read() : {0}", len);
Console.WriteLine("Position : {0}", stream.Position);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenRead("sample.dat")
' streamの長さと現在の位置を表示
Console.WriteLine("Length : {0}", stream.Length)
Console.WriteLine("Position : {0}", stream.Position)
' streamからデータを読み込む
Dim buffer(7) As Byte
Dim len As Integer = stream.Read(buffer, 0, buffer.Length)
' 読み込めたバイト数と現在の位置を表示
Console.WriteLine("Read() : {0}", len)
Console.WriteLine("Position : {0}", stream.Position)
End Using
End Sub
End Class
Length : 32 Position : 0 Read() : 8 Position : 8
現在位置の変更(シーク)を行うにはPositionプロパティに値を設定するか、Seekメソッドを使います。 また、ストリームの長さを変更するにはSetLengthメソッドを使います。
現在位置や長さを取得できないストリームに対してPosition・Lengthを参照しようとした場合、例外NotSupportedExceptionがスローされます。 例えば、NetworkStreamや標準入出力のストリームは長さや位置が取得できないストリームです。
終端のチェック
Streamクラスではバッファリングは行われず、またPeekなどの先読みを行うメソッド・プロパティは用意されないため、読み込みを行った結果ストリームの終端に達したかどうかは事前に知ることはできず、次に読み込みを行ってみるまでわかりません。
Readメソッドでは、ストリームの終端に達している場合に呼び出すと 0 が返されます。 ReadByteメソッドでは -1 が返されます。 Read・ReadByteメソッドは、ストリームの終端に達した後も呼び出すことは可能なので、その戻り値から終端に達したかどうかを判定することが出来ます。 (Streamクラスの読み込みを行うメソッドからは例外EndOfStreamExceptionがスローされることはありません。)
ストリームの現在位置と長さを表すプロパティPositionとLengthの値を調べ、両者の値が同じならストリームの終端に達したと判断することも出来ます。 ただし、NetworkStreamや、Console.OpenStandardInput等のメソッドで取得した標準入出力のストリームなどでは現在位置と長さを取得することはできず、NotSupportedExceptionがスローされてしまうため、この場合はやはりRead・ReadByteメソッドの戻り値を見るほかありません。
StreamReaderクラスにはStreamReader.EndOfStreamプロパティが用意されています。
書き込み操作
Streamクラスを使ってデータの書き込みを行う方法について。
書き込み (Write, WriteByte)
Streamにデータを書き込むには、Writeメソッドを使います。 Writeメソッドの引数は次のとおりです。
- 第1引数 buffer
- Streamに書き込むデータを格納しているバイト配列
- 第2引数 offset
- Streamに書き込むバイト配列中の開始インデックス
- 第3引数 count
- Streamに書き込むデータのバイト数
つまりWriteメソッドでは、buffer[offset]からbuffer[offset + count - 1]の範囲のデータがStreamに書き込まれることになります。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの0番目から4バイト分(buffer[0]〜buffer[3])をstreamに書き込む
stream.Write(buffer, 0, 4);
// bufferの4番目から4バイト分(buffer[4]〜buffer[7])をstreamに書き込む
stream.Write(buffer, 4, 4);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの0番目から4バイト分(buffer(0)〜buffer(3))をstreamに書き込む
stream.Write(buffer, 0, 4)
' bufferの4番目から4バイト分(buffer(4)〜buffer(7))をstreamに書き込む
stream.Write(buffer, 4, 4)
End Using
End Sub
End Class
Writeメソッドでの書き込みが成功した場合、書き込めたバイト数だけストリームの現在位置(Position)が移動します。
WriteByteメソッドを使うと、Streamにデータを1バイトずつ書き込むことができます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 書き込むデータが格納されているバイト配列
var data = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
for (var index = 0; index < data.Length; index++) {
// streamに1バイトずつ書き込む
stream.WriteByte(data[index]);
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 書き込むデータが格納されているバイト配列
Dim data() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
For index As Integer = 0 To data.Length - 1
' streamに1バイトずつ書き込む
stream.WriteByte(data(index))
Next
End Using
End Sub
End Class
フラッシュ (Flush)
Writeメソッドで書き込んだデータをフラッシュさせるには、Flushメソッドを呼び出します。 FileStreamなど、内部でバッファリングが行われるように実装されているStreamでは、Flushメソッドを呼び出すことで内部バッファに格納された内容を反映させることができます。 ただ、Closeメソッドを呼び出したりusingステートメントから抜け出る際には自動的にフラッシュされるので、Streamを閉じる前においてはFlushメソッドを呼び出す必要はありません。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの0番目から4バイト分(buffer[0]〜buffer[3])をstreamに書き込む
stream.Write(buffer, 0, 4);
// 書き込んだ内容をフラッシュする
stream.Flush();
// bufferの4番目から4バイト分(buffer[4]〜buffer[7])をstreamに書き込む
stream.Write(buffer, 4, 4);
}
// usingステートメントから抜け出る際にもDisposeメソッドにより自動的にフラッシュされる
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの0番目から4バイト分(buffer(0)〜buffer(3))をstreamに書き込む
stream.Write(buffer, 0, 4)
' 書き込んだ内容をフラッシュする
stream.Flush()
' bufferの4番目から4バイト分(buffer(4)〜buffer(7))をstreamに書き込む
stream.Write(buffer, 4, 4)
End Using
' Usingステートメントから抜け出る際にもDisposeメソッドにより自動的にフラッシュされる
End Sub
End Class
ストリームの長さの設定 (SetLength)
ストリームの長さを変更したい場合は、SetLengthメソッドを呼び出します。 SetLengthメソッドで現在のStreamの長さよりも短くする場合、その内容は切り捨てられます。 同時に、ストリームの書き込み・読み込み位置はストリームの終端に移動します。 逆に現在のStreamの長さよりも長くする場合、拡張した部分の内容は定義されません。 ストリームの読み込み・書き込み位置は変わりません。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
// streamの長さを4バイトにする
stream.SetLength(4);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
' streamの長さを4バイトにする
stream.SetLength(4)
End Using
End Sub
End Class
Positionプロパティとは異なりLengthプロパティは読み取り専用なので、このプロパティに値を設定してストリームの長さを変更することは出来ません。
長さが変更できないストリームに対してSetLengthを呼び出そうとした場合には、例外NotSupportedExceptionがスローされます。 例えば、NetworkStreamや標準入出力のストリーム、固定長に設定されたMemoryStreamなどは長さが変更できないストリームです。
既存のファイルを開いて上書きしようとする場合など、既にストリームに何らかの内容が書き込まれている場合、書き込んだ後にSetLengthでストリームの長さも変更しないと以前の内容が残ります。 例えば、FileStreamで長さ16バイトのファイルを開いて8バイトのデータを書き込む場合、SetLengthでストリームの長さも8バイトに設定しないと、ファイルの長さは16バイトのままになります。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 現在のstreamの長さを表示
Console.WriteLine(stream.Length);
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
Console.WriteLine(stream.Length);
// 現在の書き込み位置(8バイト目)をstreamの長さとして設定する
stream.SetLength(stream.Position);
Console.WriteLine(stream.Length);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 現在のstreamの長さを表示
Console.WriteLine(stream.Length)
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
Console.WriteLine(stream.Length)
' 現在の書き込み位置(8バイト目)をstreamの長さとして設定する
stream.SetLength(stream.Position)
Console.WriteLine(stream.Length)
End Using
End Sub
End Class
上記の例ではすべての書き込みが終わって長さが確定してからSetLengthメソッドを呼び出していますが、次のようにあらかじめ長さを0にしてストリームの内容を破棄してから書き込むようにすれば、実際に書き込んだ長さを後から調べる必要がなくなり実装を簡略化できます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// 現在のstreamの長さを表示
Console.WriteLine(stream.Length);
// streamの長さをいったん0にして現在の内容を破棄する
stream.SetLength(0);
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
Console.WriteLine(stream.Length);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' 現在のstreamの長さを表示
Console.WriteLine(stream.Length)
' streamの長さをいったん0にして現在の内容を破棄する
stream.SetLength(0)
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
Console.WriteLine(stream.Length)
End Using
End Sub
End Class
さらにFileStreamでは、コンストラクタにFileMode.CreateもしくはFileMode.Truncateを指定してインスタンスを作成すれば、既存のファイルを開いた場合にはその内容は破棄され、ストリームの長さは0となります。 従って、次の例は上記の例と同等の動作となります。
using System;
using System.IO;
class Sample {
static void Main()
{
// FileMode.Createを指定してFileStreamを作成
using (var stream = new FileStream("sample.dat", FileMode.Create, FileAccess.Write)) {
// 現在のstreamの長さを表示
Console.WriteLine(stream.Length);
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
Console.WriteLine(stream.Length);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' FileMode.Createを指定してFileStreamを作成
Using stream As Stream = New FileStream("sample.dat", FileMode.Create, FileAccess.Write)
' 現在のstreamの長さを表示
Console.WriteLine(stream.Length)
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
Console.WriteLine(stream.Length)
End Using
End Sub
End Class
D:\test>type sample.dat XXXXXXXXXXXXXXXX D:\test>Sample.exe 0 8 D:\test>type sample.dat ABCDEFGH
シーク (Seek)
Positionプロパティはストリーム内における現在の読み込み・書き込み位置を取得するためのプロパティですが、ランダムアクセスをサポートするストリームではPositionプロパティに値を設定することでその位置にシークすることができます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// ストリームの書き込み位置を4バイト目に移動する
stream.Position = 4;
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
// ストリームの書き込み位置を0バイト目(ストリームの先頭)に移動する
stream.Position = 0;
// bufferの4バイト分をstreamに書き込む
stream.Write(buffer, 0, 4);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' ストリームの書き込み位置を4バイト目に移動する
stream.Position = 4
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
' ストリームの書き込み位置を0バイト目(ストリームの先頭)に移動する
stream.Position = 0
' bufferの4バイト分をstreamに書き込む
stream.Write(buffer, 0, 4)
End Using
End Sub
End Class
Seekメソッドを使うことでもシークを行うことができます。 Seekメソッドでは、シーク先のオフセットoffsetとシークの原点originの二つを指定します。 originはSeekOrigin列挙体で指定し、その値によってoffsetの意味が次のように変わります。 また、offsetには負の値を指定することもできます。
SeekOrigin | 意味 | 指定例 |
---|---|---|
SeekOrigin.Begin | ストリームの先頭からoffset進めた位置にシークする (Position = offset) |
ストリームの先頭にシークする場合 Seek(0, SeekOrigin.Begin) ストリームの先頭から4バイト目にシークする場合 Seek(4, SeekOrigin.Begin) |
SeekOrigin.Current | ストリーム内の現在の読み込み・書き込み位置からoffset進めた位置にシークする (Position = Position + offset) |
現在位置から8バイト後方にシークする場合 Seek(8, SeekOrigin.Current) 現在位置から8バイト前方にシークする場合 Seek(-8, SeekOrigin.Current) |
SeekOrigin.End | ストリームの終端からoffset進めた位置にシークする (Position = Length + offset) |
ストリームの末尾にシークする場合 Seek(0, SeekOrigin.End) ストリームの末尾から4バイト手前にシークする場合 Seek(-4, SeekOrigin.End) |
次の例は、ストリームの末尾に追記を行うものです。 ストリームを開き、ストリームの末尾にシークしてから書き込みを行うことで、既存の内容の後ろに新たな内容を追記しています。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var stream = File.OpenWrite("sample.dat")) {
// ストリームの末尾までシーク
stream.Seek(0, SeekOrigin.End);
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using stream As Stream = File.OpenWrite("sample.dat")
' ストリームの末尾までシーク
stream.Seek(0, SeekOrigin.End)
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
End Using
End Sub
End Class
なおFileStreamでは、コンストラクタにFileMode.Appendを指定してインスタンスを作成すれば、ファイルを開いたあと自動的にストリームの末尾にシークされます。 従って、次の例は上記の例と同等の動作となります。
using System;
using System.IO;
class Sample {
static void Main()
{
// FileMode.Appendを指定してFileStreamを作成
using (var stream = new FileStream("sample.dat", FileMode.Append, FileAccess.Write)) {
// 書き込むデータが格納されているバイト配列
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' FileMode.Appendを指定してFileStreamを作成
Using stream As Stream = New FileStream("sample.dat", FileMode.Append, FileAccess.Write)
' 書き込むデータが格納されているバイト配列
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' bufferの8バイト分をstreamに書き込む
stream.Write(buffer, 0, 8)
End Using
End Sub
End Class
シーケンシャルアクセスのみをサポートするストリームに対してSeekメソッドを呼び出したり、Positionプロパティに値を設定しようとした場合には、例外NotSupportedExceptionがスローされます。 例えば、NetworkStreamや標準入出力のストリームはシークがサポートされないストリームです。
ケーパビリティ (CanRead, CanWrite, CanSeek)
Streamはその種類やコンストラクタでの設定によって書き込み可能か、読み込み可能か異なります。 Streamが書き込み可能かどうかといったケーパビリティを実行時に知るには、CanRead・CanWrite・CanSeekの各プロパティを参照します。
次の例は、様々なStreamに対してそのケーパビリティを調べた例です。
using System;
using System.IO;
class Sample {
static void PrintStreamCapability(Stream stream)
{
Console.WriteLine("CanRead : {0}", stream.CanRead);
Console.WriteLine("CanWrite : {0}", stream.CanWrite);
Console.WriteLine("CanSeek : {0}", stream.CanSeek);
}
static void Main()
{
// 読み込み用に開いたFileStream
Console.WriteLine("[File.OpenRead]");
using (var stream = File.OpenRead("sample.dat")) {
PrintStreamCapability(stream);
}
// 書き込み用に開いたFileStream
Console.WriteLine("[File.OpenWrite]");
using (var stream = File.OpenWrite("sample.dat")) {
PrintStreamCapability(stream);
}
// 書き込み可能なMemoryStream
Console.WriteLine("[MemoryStream]");
using (var stream = new MemoryStream()) {
PrintStreamCapability(stream);
}
// 読み込み専用のMemoryStream
Console.WriteLine("[MemoryStream (read only)]");
using (var stream = new MemoryStream(new byte[8], false)) {
PrintStreamCapability(stream);
}
// 標準入力のStream
Console.WriteLine("[Console.OpenStandardInput]");
using (var stream = Console.OpenStandardInput()) {
PrintStreamCapability(stream);
}
// 標準出力のStream
Console.WriteLine("[Console.OpenStandardOutput]");
using (var stream = Console.OpenStandardOutput()) {
PrintStreamCapability(stream);
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub PrintStreamCapability(ByVal stream As Stream)
Console.WriteLine("CanRead : {0}", stream.CanRead)
Console.WriteLine("CanWrite : {0}", stream.CanWrite)
Console.WriteLine("CanSeek : {0}", stream.CanSeek)
End Sub
Shared Sub Main()
' 読み込み用に開いたFileStream
Console.WriteLine("[File.OpenRead]")
Using stream As Stream = File.OpenRead("sample.dat")
PrintStreamCapability(stream)
End Using
' 書き込み用に開いたFileStream
Console.WriteLine("[File.OpenWrite]")
Using stream As Stream = File.OpenWrite("sample.dat")
PrintStreamCapability(stream)
End Using
' 書き込み可能なMemoryStream
Console.WriteLine("[MemoryStream]")
Using stream As Stream = New MemoryStream()
PrintStreamCapability(stream)
End Using
' 読み込み専用のMemoryStream
Console.WriteLine("[MemoryStream (read only)]")
Using stream As Stream = New MemoryStream(New Byte(7) {}, False)
PrintStreamCapability(stream)
End Using
' 標準入力のStream
Console.WriteLine("[Console.OpenStandardInput]")
Using stream As Stream = Console.OpenStandardInput()
PrintStreamCapability(stream)
End Using
' 標準出力のStream
Console.WriteLine("[Console.OpenStandardOutput]")
Using stream As Stream = Console.OpenStandardOutput()
PrintStreamCapability(stream)
End Using
End Sub
End Class
[File.OpenRead] CanRead : True CanWrite : False CanSeek : True [File.OpenWrite] CanRead : False CanWrite : True CanSeek : True [MemoryStream] CanRead : True CanWrite : True CanSeek : True [MemoryStream (read only)] CanRead : True CanWrite : False CanSeek : True [Console.OpenStandardInput] CanRead : True CanWrite : False CanSeek : False [Console.OpenStandardOutput] CanRead : False CanWrite : True CanSeek : False
各ケーパビリティと、該当する操作のメソッド・プロパティは次のとおりです。
ケーパビリティのプロパティ | 該当するStreamの操作 |
---|---|
CanWrite | Write, WriteByte, Flush, SetLength |
CanRead | Read, ReadByte |
CanSeek | Seek, SetLength, Position(プロパティの設定) |
ケーパビリティがfalse
になっている操作を行おうとすると、例外NotSupportedExceptionがスローされます。 例えば、CanSeekがfalse
のStreamに対してSeekメソッドやSetLengthメソッドを呼び出そうとすると例外エラーとなります。
上記の例で使用している標準入力・標準出力のStreamの取得については自プロセスの標準入出力 §.標準ストリームの取得で詳しく解説しています。
クローズ (Close, Dispose)
Streamに対する読み書きが終了した後、Closeメソッドを呼び出すことでStreamを閉じることができます。
using System;
using System.IO;
class Sample {
static void Main()
{
Stream stream = null;
try {
// Streamを開く
stream = File.OpenWrite("sample.dat");
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// streamにデータを書き込む
stream.Write(buffer, 0, 8);
}
finally {
// すべての処理が終わったらstreamを閉じる
if (stream != null)
stream.Close();
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Dim stream As Stream = Nothing
Try
' Streamを開く
stream = File.OpenWrite("sample.dat")
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' streamにデータを書き込む
stream.Write(buffer, 0, 8)
Finally
' すべての処理が終わったらstreamを閉じる
If stream IsNot Nothing Then stream.Close()
End Try
End Sub
End Class
StreamクラスはIDisposableインターフェイスを実装しているので、Streamをusingステートメント内で使うことが出来ます。 この場合、Closeメソッドを呼び出さなくてもusingステートメントから抜ける時点でそれに相当する処理が自動的に行われます。 そのため、上記のコードと次のコードは、同等のものとなります。
using System;
using System.IO;
class Sample {
static void Main()
{
// Streamを開き、usingステートメント内で使用する
using (var stream = File.OpenWrite("sample.dat")) {
var buffer = new byte[8] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
// streamにデータを書き込む
stream.Write(buffer, 0, 8);
}
// usingステートメントを抜ける時点でCloseメソッドに相当する処理が行われ、streamは閉じられる
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
' Streamを開き、usingステートメント内で使用する
Using stream As Stream = File.OpenWrite("sample.dat")
Dim buffer() As Byte = New Byte(7) {&h41, &h42, &h43, &h44, &h45, &h46, &h47, &h48}
' streamにデータを書き込む
stream.Write(buffer, 0, 8)
End Using
' Usingステートメントを抜ける時点でCloseメソッドに相当する処理が行われ、streamは閉じられる
End Sub
End Class
usingステートメントとIDisposableについてはオブジェクトの破棄 §.usingステートメントで詳しく解説しています。
Closeメソッドでストリームを閉じた後は、ほとんどのメソッドの呼び出しとプロパティの参照ができなくなります。 閉じたStreamに対してこれらの操作を行おうとすると例外ObjectDisposedExceptionがスローされます。
ベースとなるストリームのクローズ
GZipStreamなど他のストリームのラッパーとして動作するストリームの場合、そのストリームを閉じる際にベースとなったストリームも一緒に閉じるかどうかを指定することができるものがあります。 コンストラクタの引数leaveOpenにtrueを指定すると、Closeメソッドを呼び出したりusingステートメントから抜けてストリームを閉じた場合でも、ベースとなったストリームは開いたままになります。
次の例では、GZipStreamを使ってメモリ上で圧縮・展開を行なっていますが、圧縮と展開で同じMemoryStreamを使えるようleaveOpenにtrueを指定してGZipStreamを作成しています。
using System;
using System.IO;
using System.IO.Compression;
class Sample {
static void Main()
{
// GZipStreamで圧縮した結果を書き込むためのMemoryStreamを作成する
using (var compressedStream = new MemoryStream()) {
// データを圧縮してcompressedStreamに書き込むためのGZipStreamを作成する
// (GZipStreamを閉じてもcompressedStreamは閉じないようleaveOpenをtrueにする)
using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Compress, true)) {
// 圧縮するファイルのFileStreamを作成する
using (var inputStream = File.OpenRead("sample.txt")) {
// inputStreamからデータを読み出し、gzipStreamに書き込む
inputStream.CopyTo(gzipStream);
}
}
// この時点でgzipStreamは閉じられるが、compressedStreamは引き続き使用できる
// compressedStreamの現在位置をストリームの先頭に戻し、GZipStreamで再度展開する
compressedStream.Position = 0L;
using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress, false)) {
// StreamReaderを使って展開した内容を読み込んで表示する
var reader = new StreamReader(gzipStream);
Console.WriteLine(reader.ReadToEnd());
}
// この時点でgzipStreamは閉じられるが、leaveOpenにfalseを指定しているため、
// compressedStreamも閉じられる
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using fromStream As Stream = File.OpenRead("sample.dat")
Using toStream As New MemoryStream()
' ファイルの内容をMemoryStreamにコピー
fromStream.CopyTo(toStream)
' コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()))
End Using
End Using
End Sub
End Class
なお、StreamReader・StreamWriterやBinaryReader・BinaryWriterでもベースとなったストリームを開いたままにするかどうかを指定することができます。
コピー (CopyTo)
ストリームの内容を別のストリームにコピーするには、CopyToメソッドを使うことができます。 あるストリームの内容をコピーしてメモリ上(MemoryStream)に保持したり、ファイル(FileStream)に書き出したりしたい場合などに使えます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var fromStream = File.OpenRead("sample.dat")) {
using (var toStream = new MemoryStream()) {
// ファイルの内容をMemoryStreamにコピー
fromStream.CopyTo(toStream);
// コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using fromStream As Stream = File.OpenRead("sample.dat")
Using toStream As New MemoryStream()
' ファイルの内容をMemoryStreamにコピー
fromStream.CopyTo(toStream)
' コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()))
End Using
End Using
End Sub
End Class
CopyToメソッドでは、コピー元のStreamからReadしたものをコピー先のStreamにWriteします。 この際、コピー元・コピー先ともに現在位置からのコピーが行われます。 つまり現在位置がストリームの先頭でない場合、ストリームの途中からコピーが行われます。 コピー開始時にストリームの先頭へシークされることはありません。 また、CopyToメソッドではコピーする長さを指定することはできず、常にストリームの終端までがコピーされます。
CopyToメソッドでのコピーの際、CopyToメソッド内でデータの読み書きに使用されるバッファが確保されますが、このバッファのサイズを指定することもできます。 なお、指定しなかった場合ではデフォルトで4096バイトのバッファが確保されます。
using System;
using System.IO;
class Sample {
static void Main()
{
using (var fromStream = File.OpenRead("sample.dat")) {
using (var toStream = new MemoryStream()) {
// ファイルの内容をMemoryStreamにコピー (バッファサイズとして64[バイト]を指定)
fromStream.CopyTo(toStream, 64);
// コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Using fromStream As Stream = File.OpenRead("sample.dat")
Using toStream As New MemoryStream()
' ファイルの内容をMemoryStreamにコピー (バッファサイズとして64[バイト]を指定)
fromStream.CopyTo(toStream, 64)
' コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()))
End Using
End Using
End Sub
End Class
CopyToメソッドは.NET Framework 4以降で使用可能なメソッドです。 .NET Framework 3.5以前の場合はCopyToメソッドを使うことは出来ないので、次のようにReadメソッド・Writeメソッドを使ってコピー処理を実装する必要があります。
using System;
using System.IO;
class Sample {
static void Copy(Stream fromStream, Stream toStream, int bufferSize)
{
// 読み込みに使用するバッファを確保
var buffer = new byte[bufferSize];
for (;;) {
// コピー元のStreamからバッファのサイズ分だけデータを読み込む
int len = fromStream.Read(buffer, 0, bufferSize);
if (len == 0)
// コピー元のStreamの終端まで読み込んだらコピー終了
break;
// 読み込んだデータをコピー先のStreamに書き込む
toStream.Write(buffer, 0, len);
}
}
static void Main()
{
using (var fromStream = File.OpenRead("sample.dat")) {
using (var toStream = new MemoryStream()) {
// ファイルの内容をMemoryStreamにコピー (バッファサイズとして64[バイト]を指定)
Copy(fromStream, toStream, 64);
// コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()));
}
}
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Copy(ByVal fromStream As Stream, ByVal toStream As Stream, ByVal bufferSize As Integer)
' 読み込みに使用するバッファを確保
Dim buffer(bufferSize - 1) As Byte
Do
' コピー元のStreamからバッファのサイズ分だけデータを読み込む
Dim len As Integer = fromStream.Read(buffer, 0, bufferSize)
If len = 0 Then Exit Do ' コピー元のStreamの終端まで読み込んだらコピー終了
' 読み込んだデータをコピー先のStreamに書き込む
toStream.Write(buffer, 0, len)
Loop
End Sub
Shared Sub Main()
Using fromStream As Stream = File.OpenRead("sample.dat")
Using toStream As New MemoryStream()
' ファイルの内容をMemoryStreamにコピー (バッファサイズとして64[バイト]を指定)
Copy(fromStream, toStream, 64)
' コピーした内容をバイト配列に変換して表示
Console.WriteLine(BitConverter.ToString(toStream.ToArray()))
End Using
End Using
End Sub
End Class
非同期操作
(未整理)
BeginRead・BeginWriteなどのメソッドを使うことで、Streamを使って非同期の読み書きを行うことが出来ます。
非同期呼び出しとコールバックについてはデリゲートの機能 §.メソッドの非同期呼び出し (BeginInvoke, EndInvoke)を参照してください。
ストリームの種類によっては操作のタイムアウトもサポートしています。 CanTimeoutプロパティを参照することで操作がタイムアウト可能かどうかを知ることができます。 また、ReadTimeoutプロパティ・WriteTimeoutプロパティでタイムアウト時間の取得・設定ができます。
複数のスレッドからStreamにアクセスする場合、Synchronizedメソッドメソッドを使うとStreamに対する読み書き操作をスレッドセーフにすることができます。
.NET Framework 4.5からは、ReadAsync・WriteAsyncといった非同期操作のメソッドも使えるようになっています。
ヌルオブジェクト (Null)
Streamクラスにはヌルオブジェクトを取得するプロパティStream.Nullが用意されています。 Stream.Nullは、NULLデバイス(nul・/dev/null)の代わりとして使ったり、テスト時に具体的なStreamを用意する代わりにモックとして使用したりすることができます。
Stream.Nullは読み込み・書き込み・シークなど全ての機能をサポートしますが、実際にそれらの操作を行なっても何も起こりません。 Stream.Nullに対してWriteメソッドで書き込んだ内容はすべて破棄され、ReadメソッドでStream.Nullから読み込みを行った場合は0バイトのデータが読み出されます。 Stream.Nullの長さ(Length)は常に0です。
次の例では、出力先をStream.Nullに設定したStreamWriterをConsole.SetOutメソッド渡すことによって標準出力の出力先をコンソールからStream.Nullに変更(リダイレクト)し、Console.WriteLineメソッドで出力される内容を破棄しています。
using System;
using System.IO;
class Sample {
static void Main(string[] args)
{
foreach (var arg in args) {
// オプションスイッチ/quietが指定された場合は、標準出力へのメッセージ出力を抑止する
if (arg == "/quiet") {
// 標準出力の出力先をStream.Nullに設定したStreamWriterに変更する
Console.SetOut(new StreamWriter(Stream.Null));
// 次のようにTextWriter.Nullを使うことも可能
// Console.SetOut(TextWriter.Null);
}
}
// 警告メッセージを標準出力に出力
Console.WriteLine("warning");
// 致命的なエラーメッセージを標準エラーに出力
Console.Error.WriteLine("FATAL ERROR");
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main(ByVal args() As String)
For Each arg As String In args
' オプションスイッチ/quietが指定された場合は、標準出力へのメッセージ出力を抑止する
If arg = "/quiet" Then
' 標準出力の出力先をStream.Nullに設定したStreamWriterに変更する
Console.SetOut(New StreamWriter(Stream.Null))
' 次のようにTextWriter.Nullを使うことも可能
' Console.SetOut(TextWriter.Null)
End If
Next
' 警告メッセージを標準出力に出力
Console.WriteLine("warning")
' 致命的なエラーメッセージを標準エラーに出力
Console.Error.WriteLine("FATAL ERROR")
End Sub
End Class
D:\test> Sample.exe warning FATAL ERROR D:\test> Sample.exe /quiet FATAL ERROR
標準出力のリダイレクトについては自プロセスの標準入出力 §.標準ストリームのリダイレクトで詳しく解説しています。
構造体の読み書き
Streamクラスには任意の構造体を読み書きするメソッドは用意されていません。 ストリーム中のブロックを構造体として読み書きしたい場合、fread
/fwrite
関数のような読み書きを行いたい場合は、いったんバイト配列として読み書きし、さらにバイト配列から構造体に変換する必要があります。 具体的な実装例についてはBinaryReader・BinaryWriterでの構造体の読み書きで紹介しています。