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は列挙型です。
これにはBeginCurrentEndの三つの値があります。
これらの基準位置に、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以降にデータがある場合は上書きされます。
上のサンプルコードではmsLengthは「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