StreamReader/Writerクラス

Streamクラスとテキスト

Streamクラスでのデータの読み書きはbyte型配列(バイナリ)で行われます。
そのままではテキストデータが扱いづらいので、StreamReaderStreamWriterクラスを利用します。

StreamReader/WriterクラスはStream(の派生)クラスと組み合わせて使用します。
まずStreamのインスタンスを生成し、それをStreamReader/Writerのコンストラクターに渡します。
これでStreamReader/WriterはStreamとの間に入ってデータを適切に加工してくれます。


MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);

//swを通してmsの読み書き
sw.WriteLine("あいうえお");

sw.Close();
ms.Close();

StreamReader/Writerを閉じると操作対象のストリームも閉じるため、実は最後の「ms.Close();」は必要ありません。
(書いても問題はない)

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


using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
    //swを通してmsの読み書き
    sw.WriteLine("あいうえお");
}

BaseStreamプロパティ

StreamReader/WriterのインスタンスはBaseStreamプロパティで操作対象のストリームを取得することができます。


using (MemoryStream ms = new MemoryStream())
using (StreamReader sr = new StreamReader(ms))
{
    //BaseStreamプロパティはMemoryStreamを返すので
    //MemoryStreamのメソッドを使用可能
    sr.BaseStream.WriteByte((byte)1);
}

Encoding

StreamReader/Writerの文字コードはデフォルトでUTF-8が使用されます。
文字コードを変更する場合はコンストラクターでEncodingを指定します。
(System.Text.Encoding)


//UTF-16
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.Unicode))
{
}

//Shift-JIS
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.GetEncoding("shift-jis")))
{
}

詳しくはEncodingクラスを参照してください。

直接FileStreamを呼び出す

StreamReader/Writerのコンストラクターにファイルのパス文字列を指定すると、FileStreamを呼び出した上でStreamReader/Writerのインスタンスを生成することができます。


string path = "test.txt";

using (var sw = new StreamWriter(path))
{
    //BaseStreamプロパティはFileStreamを返す
	int length = sw.BaseStream.Length;
}

//文字コード指定
using (var sw = new StreamWriter(path, Encoding.Unicode))
{
}

StreamReaderの場合は「FileMode.Open」と同等の動作となります。
StreamWriterの場合は「FileMode.Create」と同等の動作となります。
詳しくはFileStreamクラスを参照してください。

StreamWriterクラス

文字列の書き込みにはStreamWriterクラスのWriteWriteLineメソッドを使用します。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Create))
using (StreamWriter sw = new StreamWriter(fs))
{
    sw.Write('a');                
    sw.Write("あいうえお");
    sw.Write(123);

    sw.WriteLine();

    sw.WriteLine("あいうえお");
    sw.WriteLine(456.789);
    sw.WriteLine("{0} {1}", "かき", "くけこ");
}

Writeメソッドは文字列を書き込み、WriteLineメソッドは最後に改行文字を追加で書き込みます。
引数に指定できる値はConsole入出力のWrite、WriteLineメソッドとほぼ同じで、複合書式も可能です。
数値などの文字列以外を指定した場合も文字列に変換して書き込まれます。

追記モード

StreamWriterのコンストラクターにファイルパスを指定して直接ファイルを開くとき、第二引数にtrueを指定するとファイルを追記モード(FileMode.Append)で開きます。


string path = "test.txt";

//追記モード
using (StreamWriter sw = new StreamWriter(path, true))
{
}

//追記モード&文字コード指定
using (StreamWriter sw = new StreamWriter(path, true, Encoding.Unicode))
{
}

詳しくはFileStreamクラスを参照してください。

StreamReaderクラス

文字列の読み込みにはStreamReaderクラスを使用します。
ここでは先ほど作成したファイル「test.txt」を読み込んでみます。

ReadLineメソッド

ReadLineメソッドはストリームから文字列を一行読み込みます。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    string line;
    while ((line = sr.ReadLine()) != null)
        Console.WriteLine(line);
}
aあいうえお123
あいうえお
456.789
かき くけこ

ReadLineメソッドは現在のストリームの位置から改行文字まで読み取り、string型を返します。
最後の改行文字は戻り値には含まれません。

ストリームの終端に達した場合はnullを返します。
これを利用してサンプルコードではテキストファイルをすべて読み込み表示しています。

Read、Peekメソッド

Readメソッドは1文字を読み取ります。
Peekメソッドは1文字を読み取りますが、ストリームの位置は移動させません。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    while (sr.Peek() >= 0)
        Console.Write((char)sr.Read());
}

実行結果は同じなので省略します。

ReadメソッドおよびPeekメソッドの戻り値はint型ですので、必要に応じてキャストします。
ストリームの終端に達した場合は-1を返します。

Readメソッドのオーバーロード

Readメソッドにはchar型配列に文字列を格納するオーバーロードがあります。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    char[] chars = new char[6];

    int read;

    //配列全体にコピー
    read = sr.Read(chars, 0, chars.Length);

    //配列の2番目の要素から3つ分にコピー
    read = sr.Read(chars, 2, 3);
}

//テキストファイルをすべて表示
using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    char[] chars = new char[6];
    while (sr.Read(chars, 0, chars.Length) > 0)
    {
        foreach (var c in chars)
            Console.Write(c);
    }
}	

実行結果は同じなので省略します。

第一引数はストリームから読み取ったデータを格納するchar型配列を指定します。
第二引数は配列のコピー開始番号を指定します。
第三引数は読み取る文字数を指定します。

戻り値は実際に読み取れた文字数です。
ストリームの終端に達した場合は0を返します。

ReadToEndメソッド

ReadToEndメソッドは、ストリームの終端までの文字列を返します。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    string text = sr.ReadToEnd();
    Console.WriteLine(text);
}

実行結果は同じなので省略します。

EndOfStreamプロパティ

EndOfStreamプロパティはストリームの現在位置が終端ならば真を返します。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    Console.WriteLine(sr.EndOfStream);
    sr.ReadToEnd();
    Console.WriteLine(sr.EndOfStream);
}
False
True

シークについて

StreamReader/Writerはシークはサポートしません。
BaseStreamを経由してStreamクラスのSeekメソッド呼び出しやPositionプロパティを設定することは可能ですが、読み書きされるデータとの整合性が取れなくなる可能性があります。


string path = "test.txt";

using (FileStream fs = new FileStream(path, FileMode.Create))
using (StreamWriter sw = new StreamWriter(fs))
{
    sw.WriteLine("あいうえお");
    sw.WriteLine("かきくけこ");
    sw.WriteLine("さしすせそ");
}

using (FileStream fs = new FileStream(path, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
    Console.WriteLine(sr.ReadLine());

    //ファイルの先頭から再度読み込むつもり
    sr.BaseStream.Position = 0;

    Console.WriteLine(sr.ReadLine());
    Console.WriteLine(sr.ReadLine());
}
あいうえお
かきくけこ
さしすせそ

感覚的には「あいうえお」の行が二回表示されるはずですが上手くいきません。

StreamReader/Writerは処理の高速化のために内部的にデータをバッファリング(一時保存)しています。
つまり読み書きのメソッド実行の度にストリームとデータをやり取りするのではなく、バッファにデータを保存しておきバッファがいっぱいになったらストリームに反映させます。
このバッファの読み書き位置はストリームのPositionとは無関係であるためズレが生じます。