Streamクラス
Streamとは
C#では例えばファイルの読み書きなどのデータの入出力の処理に、ストリーム(stream)という概念があります。
ストリームは「データの流れ」を意味するもので、ファイル以外にもメモリやネットワーク上のデータとのやり取りもストリームで扱うことができます。
ストリームの機能はStreamクラスから派生したクラスで提供されています。
StreamクラスはSystem.IO
名前空間に定義されているので、コード先頭のusingディレクティブに追加しておきます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//↓追加しておく
using System.IO;
以下はStreamを扱う代表的なクラスです。
クラス名 | 説明 |
---|---|
Stream | データ入出力の基底クラス(抽象クラス) 以下のクラスはすべてStreamクラスの派生クラス |
FileStream | ファイルの入出力 |
MemoryStream | メモリの入出力 |
NetworkStream | ネットワークの入出力 |
BufferStream | 他のストリームのデータの一時保存 |
Stream派生クラスはそれぞれで操作対象が異なりますが、基本的には同じように使用できます。
本来は扱い方が異なるものを、データの操作方法を共通化したものがStreamクラスです。
(それぞれの派生クラス独自の操作方法もあります)
Streamクラスは抽象クラスなので、それ自体のインスタンスは生成できません。
実際に使用するのはStreamクラスの派生クラスです。
このページではすべてのStream派生クラスで共通の操作方法を解説します。
オープンとクローズ
ストリームを使用するにはまずストリームをオープンします。
これはStream派生クラスのインスタンスを生成し、ストリームを使用可能な状態にします。
ストリームが不要になったらクローズします。
これはストリームで確保しているデータ(メモリやファイルなど)を解放する処理です。
オープンとクローズはセットで行います。
「ノートを開き、読み書きし、最後にノートを閉じる」という動作と同じことで、開かずに読み書きはできませんし、開きっぱなしはリソースの無駄使いとなります。
ストリームのオープンは基本的にnew
キーワードで新しいインスタンスを生成します。
コンストラクターの指定はそれぞれのStream派生クラスによって異なります。
Streamの種類によってはnew以外のオープン方法も用意されています。
ストリームのクローズはClose
メソッドまたはDispose
メソッドで行います。
これらのメソッドに違いはありません。
//「test.bin」を開く
FileStream fs = new FileStream("test.bin", FileMode.Open);
//何か処理...
//fsを閉じる
fs.Close();
//↓で閉じてもOK
//fs.Dispose();
ストリームをクローズした後はそのストリームに対する読み書きはできなくなります。
ストリームのオープンとクローズはusingステートメントの使用が推奨されます。
using (FileStream fs = new FileStream("test.bin", FileMode.Open))
{
//何か処理...
}
//この時点でfsはクローズ、破棄されている
このように記述すると、usingのブロックを抜けると自動的にDispose
メソッドが呼び出されます。
詳しくはusingステートメントを参照してください。
Streamの操作
Streamクラスには基本的なメソッドが定義されており、Streamの派生クラスから利用することができます。
FileStreamクラスやMemoryStreamクラスなど、種類が違っても共通の方法で読み書きができるということです。
以下は共通して使用できるメソッド/プロパティです。
以下のメソッド/プロパティの具体的な使用例は次ページからのFileStreamクラスやMemoryStreamクラスの項で改めて説明します。
ストリームの読み書き
Read(byte[] buffer, int offset, int count) | ストリームからcountバイトを読み取り、bufferのoffset番目から格納 戻り値は読み取ったバイト数 |
ReadByte() | ストリームから1バイトを読み取って返す |
Write(byte[] buffer, int offset, int count) | bufferのoffset番目からcountバイトをストリームに書き込む 戻り値は書き込んだバイト数 |
WriteByte(byte value) | ストリームにvalueを書き込む |
ストリームの読み書きはbyte型、byte型配列で行います。
そのままでは使いづらいので、使いやすくするためのクラスも用意されています。
ストリームの長さと現在位置
ストリームには「長さ」と「現在読み書きしている位置」という概念があります。
「長さ」はファイルで言えばファイルサイズと同じ意味で、ストリーム全体の大きさです。
「現在の位置」はメモ帳などで文章を編集するときに表示される「キャレット」と同じようなものです。
テキストエディタが現在のキャレットの位置に文章を追加したり削除したりするのと同じで、ストリームも「現在の位置」を基準にしてデータを読み書きします。
ストリームを読み書きすると読み書きした分だけ自動的に位置が移動します。
これらは以下のメソッドおよびプロパティで取得/設定します。
Lengthプロパティ | ストリームの長さを取得(読み取り専用) |
Positionプロパティ | ストリームの位置を取得/設定 |
Seek(long offset, SeekOrigin loc) | ストリームの位置をloc+offsetの位置にセット 戻り値はセットした新しい位置 SeekOrigin.Begin→先頭 SeekOrigin.Current→現在の位置 SeekOrigin.End→末尾 |
ストリームの位置や長さはすべてlong型で表します。
Seek
メソッドの第二引数はSeekOrigin
は列挙型です。
これにはBegin
、Current
、End
の三つの値があります。
これらの基準位置に、offsetの値を加算した位置がPosition
にセットされます。
ただし、これらはStreamの種類や開き方によって使用できない場合があります。
ストリームの状態
Streamの種類や開き方によっては読み取り/書き込み専用であったりSeekが出来なかったりします。
その状態は以下の読み取り専用プロパティで取得できます。
これらはすべてbool型です。
CanReadプロパティ | ストリームが読み取り可能か |
CanWriteプロパティ | ストリームが書き込み可能か |
CanSeekプロパティ | ストリームがSeek可能か |
CanTimeoutプロパティ | ストリームがタイムアウト可能か ネットワーク通信などで用いる |
CanSeek
プロパティが偽の場合、Position
プロパティによる位置の変更もできません。
ストリームのコピー
Stream自体を別のStreamにコピーするにはCopyTo
メソッドを使用します。
ただしこのメソッドは.NET FrameWork4.0以降に実装されたものなので、それ以前でコピーする方法はMemoryStreamクラスの項で説明します。
//「test.bin」のLengthは100とする
using (FileStream fs = new FileStream("test.bin", FileMode.Open))
using (MemoryStream ms = new MemoryStream())
{
//fsのデータをmsにコピー
fs.CopyTo(ms);
fs.Position = 10;
//fsのデータをmsにコピー
fs.CopyTo(ms);
Console.WriteLine(ms.Length);
}
190
コピーはコピー元のPositon
の位置から末尾までを、コピー先のPosition
の位置に対して行われます。
コピー先のPosition以降にデータがある場合は上書きされます。
上のサンプルコードではms
のLength
は「100 + (100 - 10) = 190」ということになります。
長さの変更
ストリームの長さはSetLength
メソッドで変更可能です。
ここではMemoryStreamクラスを例にコードを示します。
//streamのバイトを全て表示
static void PrintStream(Stream stream)
{
if (stream == null ||
!stream.CanRead ||
!stream.CanSeek)
return;
long pos = stream.Position;
stream.Position = 0;
for (int i = 0; i < stream.Length; i++)
Console.Write("{0} ", stream.ReadByte());
Console.WriteLine();
stream.Position = pos;
}
static void Main(string[] args)
{
using(var ms = new MemoryStream())
{
//適当なデータの書き込み
for (int i = 0; i < 5; i++)
ms.WriteByte((byte)i);
//全て表示
PrintStream(ms);
//長さを10に変更
ms.SetLength(10);
//全て表示
PrintStream(ms);
//長さを3に変更
ms.SetLength(3);
//全て表示
PrintStream(ms);
}
}
0 1 2 3 4 0 1 2 3 4 0 0 0 0 0 0 1 2
Streamの長さを拡張した場合、拡張された新しい領域にどのようなデータが割り当てられるかはStream派生クラスにより異なります。
Streamの長さを縮小した場合はデータは切り捨てられます。
StreamクラスのSetLength
メソッドは抽象メソッドで、実際の動作はその派生クラスで定義されます。
なお、Position
プロパティは現在のStreamの末尾を超えた位置にセットすることも可能です。
末尾より後ろの位置でデータを書き込むとそのデータの末尾位置までStreamは拡張されます。
ストリームの内容の消去
SetLength
メソッドの引数に0を指定することで、ストリームの内容を消去できます。
//streamのバイトを全て表示
static void PrintStream(Stream stream)
{
if (stream == null ||
!stream.CanRead ||
!stream.CanSeek)
return;
long pos = stream.Position;
stream.Position = 0;
for (int i = 0; i < stream.Length; i++)
Console.Write("{0} ", stream.ReadByte());
Console.WriteLine();
stream.Position = pos;
}
using (MemoryStream ms = new MemoryStream())
{
ms.Write(new byte[] { 1, 2, 3 }, 0, 3);
PrintStream(ms);
//全て消去
ms.SetLength(0);
PrintStream(ms);
}
1 2 3