FileStreamクラス

ストリームを利用したファイルの読み書き

ファイルの読み書きはテキストファイルの読み書きバイナリファイルの読み書きで説明したFileクラスのメソッドを使用するのが簡単な方法です。
しかしこれらはあまり細かい制御はできないので、その場合はFileStreamクラスを利用します。

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

ファイルを開く

ファイルを開くにはnewキーワードでFileStreamクラスのインスタンスを生成します。
以下はファイルを開いて閉じるだけのコードですが、実行すると指定のパスに空のファイルが生成されます。
ファイルがすでに存在する場合は上書きされるので注意してください。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//↓追加しておく
using System.IO

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = "test.bin";

            FileStream fs = new FileStream(
                path, FileMode.Create, FileAccess.ReadWrite);

            //読み書き処理...

            fs.Close();

            Console.WriteLine("プログラム終了");
            Console.ReadLine();
        }
    }
}

ファイルの場所の指定は相対パスでも絶対パスでも可能です。

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


using (FileStream fs = new FileStream(
    path, FileMode.Create, FileAccess.ReadWrite))
{
    //読み書き処理...
}
//この時点でfsはクローズ、破棄されている

コンストラクター

コンストラクターの第一引数には開きたいファイルのパスを指定します。
第二引数はファイルのオープンモードFileMode列挙型で指定します。

FileMode列挙型
名前 説明 読込 書込 ファイルが存在 ファイルが無い
Append ファイルの末尾にデータを追加する。
末尾よりも前にシークはできない。
(IOException)
不可 必須 追記用に開く 新規作成
Create 新しいファイルを作成する。 必須 データを消去して開く 新規作成
CreateNew 新しいファイルを作成する。 必須 例外(IOException) 新規作成
Truncate 新しいファイルを作成する。 必須 データを消去して開く 例外(FileNotFoundException)
Open ファイルを開く。 開く 例外(FileNotFoundException)
OpenOrCreate ファイルを開く。 開く 新規作成

同じような動作をするモードがありますが、指定のファイルがすでに存在する場合/しない場合の動作が異なります。

第三引数にはFileAccess列挙型を指定しますが、これは省略可能です。
FileAccess列挙型の値は以下です。

Read
読み込み専用で開く
Write
書き込み専用で開く
ReadWrite
読み書き両用で開く

FileMode列挙型で書き込みが「必須」となっているものは、FileAccess.Readを指定すると例外(ArgumentException)が発生します。
FileMode.Appendは読み込み不可なので、FileAccess.Writeを指定する必要があります。
FileMode.Createは読み込み可/書き込み必須なので、FileAccess.Write/FileAccess.ReadWriteを指定できます。
FileMode.Openは特に制限がないので、FileAccess列挙型の値は自由に指定できます。

FileAccess列挙型の指定を省略した場合、第二引数がFileMode.AppendならばFileAccess.Writeで、それ以外ならばFileAccess.ReadWriteで開かれます。

FileStreamクラスのコンストラクターはここで説明した以外のオーバーロードも存在します。

Fileクラスを利用したオープン

FileStreamのインスタンスはFileクラスのメソッドでも生成できます。


string path = "test.bin";

//FileMode.Open, FileAccess.Read
using (FileStream fs = File.OpenRead(path))
{
}

//FileMode.Create, FileAccess.Write
using (FileStream fs = File.OpenWrite(path))
{
}

using (FileStream fs = File.Open(path, FileMode.CreateNew))
{
}

using (FileStream fs = File.Open(path, FileMode.CreateNew, FileAccess.Write))
{
}

File.OpenReadFile.OpenWriteメソッドはFileModeとFileAccessが固定です。
File.Openメソッドは上記で説明したFileStreamのコンストラクターと同じ動作となります。

書き込み

FileStreamクラスのデータの読み書きはbyte型、もしくはbyte型の配列で行われます。

Writeメソッド

ファイルへのデータの書き込みはWriteメソッドで行います。


string path = "test.bin";

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

using (FileStream fs = new FileStream(path, FileMode.Create))
{
    //配列全体を書き込み
    fs.Write(bytesNum, 0, bytesNum.Length);

    //配列の2番目の要素から6つ分を書き込み
    //(いうえ)
    fs.Write(bytesStr, 2, 6);
}

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

戻り値はありません。

WriteByteメソッド

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


string path = "test.bin";

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

using (FileStream fs = new FileStream(path, FileMode.Create))
{
    foreach (var b in bytesNum)
        fs.WriteByte(b);

    for(int i = 2; i < 8; i++)
        fs.WriteByte(bytesStr[i]);
}

戻り値はありません。

Flushメソッド

ファイルに実際にデータが書き込まれるのは、ストリームのClose(Dispose)メソッドを呼び出した時です。
(usingステートメントを使用している場合はusingブロックを抜けたとき)
ストリームを閉じずにデータを反映したい場合はFlushメソッドを呼び出します。


string path = "test.bin";

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

using (FileStream fs = new FileStream(path, FileMode.Create))
{//この時点でファイルは生成される

    foreach (var b in bytesNum)
        fs.WriteByte(b);

    fs.Flush(); //bytesNumのデータがファイルに反映される

    for(int i = 2; i < 8; i++)
        fs.WriteByte(bytesStr[i]);
}//すべてのデータがファイルに反映される

戻り値はありません。

読み込み

Readメソッド

ファイルからデータを読み込むにはReadメソッドを使用します。
ここでは上のWriteメソッドで出力した「test.bin」ファイルを読み込んでみます。


string path = "test.bin";

byte[] bytesNum = BitConverter.GetBytes(0);
byte[] bytesStr = Encoding.Unicode.GetBytes("abcde");

using (FileStream fs = new FileStream(path, FileMode.Open))
{
    //配列全体に格納
    //(4バイト読み取り)
    fs.Read(bytesNum, 0, bytesNum.Length);

    //配列の2番目の要素から6つ分に格納
    //(6バイト読み取り)
    fs.Read(bytesStr, 2, 6);
}

Console.WriteLine(BitConverter.ToInt32(bytesNum, 0));
Console.WriteLine(Encoding.Unicode.GetString(bytesStr));

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

Readメソッドはストリームから指定のバイト数を読み取り配列に格納します。
データは単純なバイト列なので、Writeメソッドで書き込んだ際と同じ順番、同じサイズでデータを読み取ることで元のデータに復元できます。

戻り値は読み取ったバイト数をint型で返します。
基本的には第三引数の値と一致しますが、ストリームの残りのデータが指定のバイト数に満たない場合は少ない値が返ってきます。
読み取れなかった場合は0が返るので、ストリームの末尾と判断できます。


string path = "test.bin";

byte[] buf = new byte[3];

using (FileStream fs = new FileStream(path, FileMode.Open))
{
    int read;
    while((read = fs.Read(buf, 0, buf.Length)) > 0)
    {
        for(int i = 0; i < read; i++)
            Console.Write("{0:X2} ", buf[i]);
    }
}
Console.ReadLine();
39 30 00 00 40 30 46 30 48 30 

ReadByteメソッド

ReadByteメソッドはストリームから1バイトを読み取ります。


string path = "test.bin";

using (FileStream fs = new FileStream(path, FileMode.Open))
{
    int read;
    while ((read = fs.ReadByte()) >= 0)
        Console.Write("{0:X2} ", (byte)read);
}

戻り値は読み取った1バイトのデータ(int型)です。
1バイトですがint型なので必要に応じてキャストします。
ストリームの末尾に達した場合は-1を返します。

ファイルの長さの変更

FileStreamの長さはSetLengthメソッドで変更可能です。
また、Positionプロパティに現在の長さ以上の値をセットし、その位置に何かデータを書き込むとストリームの長さは拡張されます。

これらの方法でストリームを拡張した場合、拡張された新しい領域に入っているデータは不定です。
0で初期化されたりはしないので、それを期待したコードにすると予期しない動作になる可能性があります。
(Linux環境では0で埋められるそうです)