StreamReaderクラスおよびStreamWriterクラスはStreamに対してテキストの読み書きを行うためのクラスです。 Stream単体ではバイナリレベルでの読み書きしか行えませんが、StreamとStreamReader・StreamWriterを組み合わせて使うことでテキストレベルの読み書きが可能になり、ファイルに対して1行ずつ読み書きするといったことができるようになります。
なお、StreamReader・StreamWriterはTextReader・TextWriterから派生したクラスです。 同じくTextReader・TextWriterから派生したクラスにStringReader・StringWriterが存在します。 こちらはStreamのかわりにString・StringBuilderに対してテキストの読み書きを行うためのクラスです。
StreamReaderのメソッドおよびStreamWriterのメソッドはどれもTextReader・TextWriterを継承したもので、そのほとんどはStringReader・StringWriterでも同じように動作します。
導入
StreamReader・StreamWriterの具体的な使い方に入る前に、StreamReader・StreamWriterを扱う上で念頭に置いておくべきことについて解説します。
StreamReader・StreamWriterとエンコーディング
StreamReader・StreamWriterでは、Encodingクラスを使った文字列のデコード・エンコードを行う機能が組み込まれています。 コンストラクタに目的のEncodingインスタンスを指定するだけで、UTF-8を始め、Shift_JISやEUC-JPなどの文字コードを深く意識すること無く簡単に扱うことができます。
Shift_JIS以外にも、Encoding.GetEncodingメソッドにコードページ番号やエンコーディング名を指定することによって、目的の文字コードに対応したEncodingを取得することができます。 具体的な取得方法や、取得できるEncodingについてはファイル入出力 §.Encodingの取得を参照してください。
Encodingを指定しなかった場合には、デフォルトでUTF-8が使用されます。 実行環境のデフォルトのエンコーディングではないので注意してください。 Streamから読み込もうとしているデータのエンコーディングと、StreamReaderに指定するエンコーディングが異なる場合(たとえばShift_JISのファイルを読もうとしてUTF-8を指定したStreamReaderを使った場合)は、当然文字化けが起こるので注意してください。
またStreamWriterでは、Encodingの指定の仕方によってはバイト順マーク(BOM)が書き込まれます。 BOMの扱いについてはEncodingクラスとBOMありなしの制御で詳しく解説していますので必要に応じて参照してください。 StreamReaderでのBOMの扱いについては、こちらで解説しています。
読み込み・書き込み対象とStreamReader・StreamWriterの作成方法
任意のStreamからの読み込み・書き込み
StreamReader・StreamWriterでは読み込み・書き込み対象となるStreamを指定してインスタンスを作成します。 ファイルであればFileStream、バイト配列(メモリ上の領域)であればMemoryStreamなど、データソースとなるStreamを読み込み・書き込み対象として指定します。
読み込み・書き込み対象がファイルの場合は、直接ファイル名を指定してStreamReader・StreamWriterインスタンスを作成することもできます。
ファイルからの読み込み・書き込み
ファイルからの読み書きを行う場合、StreamReader・StreamWriterに直接ファイル名を指定することによりFileStreamインスタンスの作成を省略することが出来ます。 StreamReader・StreamWriterのコンストラクタでファイル名を指定した場合、自動的にFileStreamの作成が行われ、指定されたファイルに対して読み書きを行うStreamReader・StreamWriterを作成することができます。
後述するFileクラスのOpenText等のメソッドを使うことでもStreamReader・StreamWriterのインスタンスを作成することができます。
StreamReader
ここではStreamReaderの使い方について解説します。 このセクションで紹介するサンプルコードでは、簡単化のためファイル名を指定してStreamReaderを作成していますが、FileStreamやMemoryStreamなどのStreamを指定してStreamReaderのインスタンスを生成した場合も同じように動作します。 また、エンコーディングの指定も省略しています。 サンプル中で使用されるファイルsample.txtは、UTF-8でエンコードされているテキストファイルを想定しています。
1行ずつの読み込み (ReadLine)
ReadLineメソッドは、ストリームから1行分の文字列を読み込みます。
EndOfStreamプロパティを参照すると、ストリームの末尾まで読み込んだかどうかを調べることができます。 これらを組み合わせてストリームの末尾までを一行ずつ読み込むには次のようにします。
ReadLineメソッドでは、ストリームの末尾に到達している場合にはnull/Nothingが返されます。 末尾以外の空行(改行文字のみの行)の場合は、null/Nothingではなく長さ0の文字列(String.Empty)が返されます。 ReadLineメソッドの戻り値がnull/Nothingかどうかを調べることでもEOFの判定が行えます。 そのため、上記の例は次のようにも書き換えられます。
ReadLineメソッドでは、CR+LF、CR(キャリッジリターン)、LF(ラインフィード)の各改行コードまでを1行として扱います。 StreamReaderでは、どの改行コードを行区切りとして扱うかを指定することは出来ません(StreamWriterではStreamWriter.NewLineプロパティで変更可能です)。
また、ReadLineメソッドの戻り値には改行文字は含まれません。 したがって戻り値に対してchomp
/chop
などの操作を行う必要はありませんが、一方、改行文字を維持して1行ずつ読み込むことはできません。
参考までに、chomp
相当の操作を行うにはString.TrimEndメソッドが使えます。
File.ReadAllLines, ReadLinesメソッドを使った読み込み
読み込み元がファイルに限定される場合は、行ごとの読み込みにFileクラスのメソッドを使うこともできます。 File.ReadAllLinesメソッドを使うと、ファイルを読み込み行ごとに分割したものを文字列配列として取得できます。 次の例では、File.ReadAllLinesでファイルを読み込み行番号を付けて表示しています。 比較のためにStreamReader.ReadLineを使って同等の処理を行う例も併記しています。
.NET Framework 4からはFile.ReadLinesメソッドも使うことができるようになっています。 File.ReadAllLinesはファイル全体の読み込みが終わるまで結果が返されないのに対し、File.ReadLinesでは一行読み込むごとに結果が返されます。 巨大なファイルを読み込む場合や、ファイルの先頭部分のみを読み込みたい場合などではReadAllLinesメソッドを使うよりもReadLinesメソッドを使った方が効率的です。
この例で使用しているイテレータ構文についてはイテレータを参照してください。
Fileクラスのメソッドを使ったファイルの読み込みについてはファイル入出力を参照してください。
全テキストの読み込み (ReadToEnd)
ReadToEndメソッドは、ストリームの現在位置から末尾までをひとつながりの文字列として読み込みます。
File.ReadAllTextを使った読み込み
読み込み元がファイルに限定される場合はFileクラスのメソッドを使うこともできます。 File.ReadAllTextメソッドを使うと、StreamReaderを使わずにファイルの内容を文字列として読み込むことができます。
Fileクラスのメソッドを使ったファイルの読み込みについてはファイル入出力を参照してください。
文字単位での読み込み (Read, Peek)
Readメソッドは、ストリームから1文字ずつ読み込みます。 Readメソッドで読み込まれるのは1バイトずつではありません。 戻り値はint/Integerで、ストリームの末尾に達した場合は-1が返されます。 それ以外の場合は読み込めた文字(char)の値となるため、戻り値をchar/Charにキャストして使います。
Peekメソッドを使うと、次の1文字を先読みすることができます。 Peekメソッドの戻り値はReadメソッドの戻り値と同様です。
次の例では、ReadメソッドとPeekメソッドを使って読み込みを行い、各行に行番号を付けて表示しています。 この例ではCR, LF, CR+LFを改行として扱い、それらの改行文字は変換せずそのまま表示しています。
引数を指定せずにReadメソッドを呼び出した場合は1文字ずつの読み込みが行われますが、Readメソッドでは読み込み先のバッファと文字数を指定して読み込むこともできるようになっています。 文字を1文字ずつチェックする必要がある場合などでは、Readメソッドで1文字ずつ読み込んでチェックするよりも、バッファに複数文字読み込んでチェックした方がメソッド呼び出しの回数が少なくなり効率的です。
次の例では、Readメソッドで一旦バッファに読み込んでから1文字ずつチェックして全角英字を半角英字に置き換えています。
ブロッキングを行う読み込み (ReadBlock)
ReadBlockメソッドは、読み込み先のバッファと文字数を指定してReadメソッドを呼び出した場合と基本的には同じです。
ReadメソッドとReadBlockメソッドの違いは、読み込みに際してブロッキングが行われるかどうかという点にあります。 Readメソッドではブロッキングされませんが、ReadBlockメソッドではブロッキングされます。
ReadBlockメソッドは、ストリームの末尾に到達するか、指定された文字数の読み込みが終わるまで処理を返しません。 一方Readメソッドは、ストリームの末尾に到達した場合のほか、指定された文字数よりも少ない文字数しか読み込めなかった場合でも処理を返します。 これはNetworkStreamを使った読み込み中にデータの途中までを受信した場合などに起こる可能性があります(FileStreamやMemoryStreamでは通常起こらないと思われます)。 従って、データの途中でも読み込みができた時点で結果を返させるようにしたければReadメソッド、指定した文字数を読み込めた時点で結果を返させるようにしたい場合にはReadBlockメソッドを使います。
こういった動作の違いについては、Stream.Readメソッドの動作についてもあわせてご覧ください。
StreamReaderとBOMの扱い
先頭にBOMの付いたストリームをStreamReaderで読み込む場合、ReadToEndやReadLineメソッドからはBOMが除去された文字列が返されます。 この動作はエンコーディングを指定した場合・省略した場合のどちらでも同じです。 ReadToEnd・ReadLine以外のメソッドでも同様にBOMの除去された文字列が返されます。
BOMを含めたまま読み込みを行いたい場合は、Streamから直接読み込むか、BinaryReaderクラスを使って読み込み処理を記述する必要があります。
StreamWriter
ここではStreamWriterの使い方について解説します。 このセクションで紹介するサンプルコードでは、簡単化のためファイル名を指定してStreamWriterを作成していますが、FileStreamやMemoryStreamなどのStreamを指定してStreamWriterのインスタンスを生成した場合も同じように動作します。 また、エンコーディングの指定も省略しています。
改行付きの書き込み (WriteLine)
WriteLineメソッドは、ストリームに1行分の文字列を書き込みます。
WriteLineメソッドでは、書き込みの際に改行文字が付け加えられます。 デフォルトでは改行文字としてプラットフォームの改行文字が使われますが、NewLineプロパティに値を設定することで使用する改行文字を変更することができます。
また、WriteLineメソッドに文字列以外を引数に指定した場合は、文字列に変換した上で書き込まれます。 複合書式設定を使用することで書式を指定したり0埋め・右詰め・左詰めした上で書き込むこともできます。
文字列への変換に際して、現在のスレッドのカルチャが書式プロバイダとして使用されます。 これはFormatProviderプロパティで参照可能です。 書式指定子や書式プロバイダについての詳細は以下のページをご覧ください。
改行無しの書き込み (Write)
Writeメソッドは、WriteLineメソッドとは異なり書き込みの際に改行文字が追記されません。 それ以外はWriteLineメソッドと同じです。
書き込む改行文字や改行の位置を細かく指定したい場合にはWriteメソッドを使うことができます。
改行文字
StreamWriterのWriteLineメソッドは、デフォルトではプラットフォームの改行文字を使用します(ランタイム・システム・プラットフォームの情報 §.改行文字)。 NewLineプロパティに値を設定することでStreamWriterが使用する改行文字を変更することができます。 NewLineプロパティではCR(\r)やLF(\n)以外の文字を含めたり、長さ0の文字列を設定することもできます。
ファイルへの追記
StreamWriterのコンストラクタでファイル名を指定する場合、同時にファイルを追記モードで開くかどうかを指定することができるようになっています。 コンストラクタの引数appendにtrueを指定すれば、ファイルの末尾から書き込みを開始するようにしたStreamWriterインスタンスが作成されます。 省略した場合やfalseを指定した場合は上書きモードでファイルが開かれ、既存のファイルを開く場合にはその内容が破棄されます。
Fileクラスを使った書き込み
書き込み先がファイルで、書き込む内容がすでに文字列として存在している場合は、Fileクラスのメソッドを使って書き込むこともできます。 File.WriteAllTextメソッドを使うと、指定した文字列を指定したファイルに書き込むことができます。 このメソッドを使うことでStreamWriterを作成せずにファイルへの書き込みが行えるため、書き込み処理の記述を簡略化することができます。
File.WriteAllTextメソッドでは既存のファイルに対する書き込みの場合、ファイルの内容は上書きされますが、File.AppendAllTextメソッドを使うと既存のファイルの内容に追記することができます。
File.WriteAllLinesメソッドを使うと、文字列配列の各要素を1行としてファイルに書き込むことができます。 File.WriteAllLinesメソッドでは、書き込みの際に自動的に改行文字が付加されます。
.NET Framework 4からは文字列配列だけでなく任意のIEnumerable<string>を指定することもできるようになっています。 そのため、書き込みたい内容がList<string>に格納されている場合でも配列に変換する必要はなくなりました。 また、.NET Framework 4からはFile.AppendAllLinesメソッドも追加されていて、このメソッドを使うことで文字列配列をファイルに追記することができます。
Fileクラスのメソッドを使ったファイルの書き込みについてはファイル入出力を参照してください。
Fileクラスのメソッドを使ったインスタンスの作成
Fileクラスのメソッドを使ってFileStreamを作成できるのと同様、Fileクラスには指定したファイルを開いてStreamReader・StreamWriterを作成するためのメソッドがいくつか用意されています。
File.OpenTextメソッドを使うと、ファイル名を指定するだけでStreamReaderを作成することができます。 このメソッドを使うと、FileStreamインスタンスの作成を省略してStreamReaderを作成することができます。
同様に、File.CreateTextメソッドを使うと、ファイル名を指定するだけでStreamWriterを作成することができます。 また、File.AppendTextメソッドを使えば、ファイルへの追記を行うStreamWriterを作成できます。
なお、これらのメソッドではエンコーディングを指定することはできないので、UTF-8以外で読み書きを行いたい場合はStreamReader・StreamWriterのコンストラクタを使用してインスタンスを作成する必要があります。
StreamReader・StreamWriterとベースとなるストリームのクローズ
StreamReader・StreamWriterでは、Closeメソッドを呼び出したりusingステートメントから抜けた際、ベースとなったストリームも閉じられ、ストリームに対するアクセスができなくなります。
StreamReader・StreamWriterを使用したあとも引き続きベースとなったストリームを使い続けたい場合は、usingステートメントは使わず、使用したStreamReader・StreamWriterを閉じないようにします。
ただ、StreamWriterでは閉じる際に同時にフラッシュも行われるようになっているため、usingステートメントやCloseメソッドでStreamWriterを閉じない場合は、StreamWriterを使い終わった時点でFlushメソッドを呼び出し、書き込んだ内容をフラッシュさせるようにする必要があります。
.NET Framework 4.5以降では、コンストラクタの引数leaveOpenにtrueを指定すると、StreamReader・StreamWriterを閉じてもベースとなるストリームを開いたままにすることができます。 ただし、leaveOpenを指定する場合は、同時に引数encoding、detectEncodingFromByteOrderMarksおよびbufferSizeも省略せずに指定する必要があります。
ベースとなるストリームの扱いについてはBinaryReader・BinaryWriterでも同様で、.NET Framework 4.5以降ではBinaryReader・BinaryWriterにおいても使用するストリームを開いたままにするかどうかをコンストラクタで指定できるようになっています。
直接ファイル名を指定してインスタンスを作成した場合や、File.OpenTextなどのメソッドを使ってインスタンスを作成した場合は、かならず作成したStreamReader/StreamWriterを閉じるようにしなければなりません。 これらのメソッドではFileStreamが内部的に作成されますが、StreamReader/StreamWriterから閉じないかぎり内部的に作成されたFileStreamは閉じられません。 従って、StreamReader/StreamWriterによって閉じるまではファイルは開かれたままとなるため、この間他からファイルへのアクセスを行うことができなくなります。