MemoryStreamクラス

メモリへの読み書き

FileStreamクラスの項ではストリームを利用してファイルを読み書きする方法を紹介しましたが、ファイルとして保存する必要がない場合はMemoryStreamを利用します。
MemoryStreamはストレージ(HDDやSSDなど)ではなくメモリにデータを読み書きします。

Streamに関してはStreamを参照してください。
このページの解説は上記ページを読んでいることが前提となっています。

このページではusingステートメントを利用したコードで記述します。


using (MemoryStream ms = new MemoryStream())
{
    //読み書き処理...
}

データの読み書き

ストリームへのデータの書き込みはWriteWriteByteメソッドで行います。
ストリームからのデータの読み込みはReadReadByteメソッドで行います。


byte[] bytesNum1 = BitConverter.GetBytes(12345);
byte[] bytesStr1 = Encoding.Unicode.GetBytes("あいうえお");
byte b1 = 123;

byte[] bytesNum2 = BitConverter.GetBytes(0);
byte[] bytesStr2 = Encoding.Unicode.GetBytes("abcde");
byte b2 = 0;

using (MemoryStream ms = new MemoryStream())
{
    //配列全体を書き込み
    ms.Write(bytesNum1, 0, bytesNum1.Length);

    //配列の2番目の要素から6つ分を書き込み
    ms.Write(bytesStr1, 2, 6);

    //1バイトを書き込み
    ms.WriteByte(b1);

    //ストリームの位置を先頭にセット
    ms.Position = 0;

    int read;

    //配列の要素数分を読み込み
    read = ms.Read(bytesNum2, 0, bytesNum2.Length);

    //6バイトを読み取り配列の2番目の要素から格納
    read = ms.Read(bytesStr2, 2, 6);

    //1バイト読み取り
    b2 = (byte)ms.ReadByte();
}

Console.WriteLine(BitConverter.ToInt32(bytesNum2, 0));
Console.WriteLine(Encoding.Unicode.GetString(bytesStr2));
Console.WriteLine(b2);

Console.ReadLine();
12345
aいうえe
123

Writeメソッドはbyte型配列をストリームに書き込みます。
第一引数はストリームに書き込む配列を指定します。
第二引数は配列のコピー開始位置を指定します。
第三引数は書き込むバイト数を指定します。

WriteByteメソッドは1バイトのデータをストリームに書き込みます。

Readメソッドは指定のバイト数をストリームから読み取り配列に格納します。
第一引数はデータを格納する配列を指定します。
第二引数は配列のコピー開始位置を指定します。
第三引数は読み取るバイト数を指定します。
戻り値は実際に読み取れたバイト数(int型)で、「0」が返ってくるとストリームの終端となります。

ReadByteメソッドは1バイトのデータをストリームから読み取り、int型で返します。
「-1」が返ってくるとストリームの終端となります。

これはFileStreamクラスと共通ですので詳しくはそちらを参照してください。

バイト型配列に変換

MemoryStreamはストリームのデータをToArrayメソッドでバイト型配列に変換できます。


byte[] bytesNum1 = BitConverter.GetBytes(12345);
byte[] bytesStr1 = Encoding.Unicode.GetBytes("あいうえお");
byte b1 = 123;

byte[] bytes;

using (MemoryStream ms = new MemoryStream())
{
    ms.Write(bytesNum1, 0, bytesNum1.Length);
    ms.Write(bytesStr1, 2, 6);
    ms.WriteByte(b1);

    //MemoryStreamをbyte型配列に変換
  bytes = ms.ToArray();
}

File.WriteAllBytes("test.bin", bytes);

このサンプルコードはMemoryStreamの内容をbyte型配列に変換し、ファイルに書き出しています。

データのクリア

MemoryStreamに書き込んだデータを消去するにはSetLengthメソッドを使用します。
このメソッドは消去というよりはストリームのサイズを調整するものですが、引数に「0」を指定することで内容を消去できます。


using (MemoryStream ms = new MemoryStream())
{
    ms.Write(new byte[] { 1, 2, 3 }, 0, 3);
    foreach(var b in ms.ToArray())
        Console.Write("{0} ", b);

    Console.WriteLine();

    ms.SetLength(0);

    ms.Write(new byte[] { 7, 8, 9 }, 0, 3);
    foreach (var b in ms.ToArray())
        Console.Write("{0} ", b);
}
1 2 3 
7 8 9 

Capacityプロパティ

MemoryStreamは実際に保存しているデータのほかに、データ保存のためにあらかじめ確保してるメモリ領域があります。
メモリの確保はそこそこコストが掛かる処理なので、大きめにメモリ領域を確保することで高速化しています。
この領域のサイズはCapacityプロパティで取得/設定できます。
(実際のデータサイズはLengthプロパティで取得可能)


using (MemoryStream ms = new MemoryStream())
{
    //何か処理...

    //Lengthを0にセット
    ms.SetLength(0);

    //Capacityを128に設定
    ms.Capacity = 128;
}

CapacityにLengthよりも小さい値を設定すると例外(ArgumentOutOfRangeException)が発生します。

コンストラクター

MemoryStreamのコンストラクターにはいくつかのオーバーロードがあります。

オーバーロード 説明 書き込み サイズ変更
MemoryStream() 空のストリームを生成
MemoryStream(int capacity) 初期サイズを指定して空のストリームを生成
MemoryStream(byte[] buffer) bufferに対するストリームを生成 ×
MemoryStream(byte[] buffer, bool writable) bufferに対するストリームを生成 writableで指定 ×
MemoryStream(byte[] buffer, int index, int count) buffer[index]~buffer[index+count-1]までの範囲に対するストリームを生成 ×
MemoryStream(byte[] buffer, int index, int count, bool writable) buffer[index]~buffer[index+count-1]までの範囲に対するストリームを生成 writableで指定 ×
MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) buffer[index]~buffer[index+count-1]までの範囲に対するストリームを生成
publiclyVisibleをtrueにするとGetBufferメソッドでバイト配列が得られる
writableで指定 ×

capacityはCapacityプロパティの初期サイズを指定してMemoryStreamを生成します。

第一引数にbyte型配列を指定するオーバーロードは、指定のbyte型配列を直接操作するMemoryStreamを生成します。
MemoryStreamでデータを変更すると元のbyte配列のデータも変更されます。
MemoryStreamのサイズは変更不可となります。
書き込みは可能ですが、writableをfalseに指定すると読み取り専用となります。

GetBuffer

最後のコンストラクターのpubliclyVisibleをtrueに指定すると、GetBufferメソッドが使用できます。
GetBufferメソッドは操作対象となっているbyte配列自体を返します。


static void Test(MemoryStream ms)
{
    byte[] bytes = ms.GetBuffer();
    for (int i = 0; i < bytes.Length; i++)
    {
        bytes[i] *= 2;
    }
}

static void Main(string[] args)
{
    byte[] bytes1 = new byte[]
        { 1, 2, 3 };

    using (MemoryStream ms = new MemoryStream
        (bytes1, 0, bytes1.Length, true, true))
    {
        Test(ms);

        byte[] bytes2 = ms.ToArray();
        foreach (var b in bytes2)
            Console.Write("{0} ", b);
    }
    
    Console.ReadLine();
}
2 4 6

ToArrayメソッドはMemoryStreamの内容をコピーしたbyte配列が返されますが、GetBufferメソッドは操作対象のbyte配列そのものを返します。
つまり戻り値のbyte配列のデータを変更するとMemoryStreamのデータも変更されます。
MemoryStream以外の方法でbyte配列を操作するような場合や、ToArrayメソッドのコピーに掛かるコストを省く場合に使用します。

publiclyVisibleがfalseになっているMemoryStreamに対してGetBufferメソッドを呼び出すと例外が発生します。

MemoryStreamのコピー

MemoryStreamを別のストリームにコピーするにはWriteToメソッドを使用します。


byte[] bytesNum = BitConverter.GetBytes(12345);
byte[] bytesStr = Encoding.Unicode.GetBytes("あいうえお");
byte b = 123;

string output = "test.bin";

using (MemoryStream ms = new MemoryStream())
using (FileStream fs = new FileStream(output, FileMode.Create))
{
    ms.Write(bytesNum, 0, bytesNum1.Length);
    ms.Write(bytesStr, 2, 6);
    ms.WriteByte(b);

    ms.WriteTo(fs);
}

Streamのコピー

MemoryStream以外のStreamを別のStreamにコピーする方法を紹介します。

.Net Framework4.0以降ではStreamクラスにCopyToメソッドが追加されているので、Streamのコピーはこれを使用するのが簡単です。
それ以前のバージョンでは以下のようなコードでコピーします。


/// <summary>
/// ストリームをコピーする。
/// </summary>
/// <param name="from">コピー元のストリーム</param>
/// <param name="to">コピー先のストリーム</param>
static void CopyStream(Stream from, Stream to)
{
    if (!from.CanRead) return;
    if (!to.CanWrite) return;

    byte[] buf = new byte[65536];

    while (true)
    {
        //読み取ったバイト数を取得
        int read = from.Read(buf, 0, buf.Length);
        if (read == 0)
            break;

        //読み取ったバイト数分を書き込み
        to.Write(buf, 0, read);
    }
}

static void Main(string[] args)
{
    string input = "test.bin";
    string output = "test_output.bin";

    using (var fsIn = new FileStream(input, FileMode.Open))
    using (var fsOut = new FileStream(output, FileMode.Create))
    {
        CopyStream(fsIn, fsOut);
    }
    Console.WriteLine("終了します。");

    Console.ReadLine();
}