【Amazon.co.jp限定】 バッファロー WiFi ルーター 無線 LAN Wi-Fi5 11ac ac1200 866+300Mbps IPv6 WPA3 デュアルバンド 日本メーカー 【 iPhone 16e / 16 / 15 / 14 / Nintendo Switch / PS5 動作確認済み 】 エコパッケージ WCR-1166DHPL/N
¥3,380 (2025年4月25日 13:08 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
皆さんは Delphi でのテキストファイルの読み書きに何を使ってますか?TStringList ですか?TFileStremですか?昔ながらの Read(ln) / Write(ln) でしょうか?
今回は Unicode 版 Delphi であればもれなく使える TStreamReader と TStreamWriter のお話です。
.NET 互換の TStreamReader / TStreamWriter は Delphi 2009 で実装されました。
TStreamReader / TStreamWriter は TStringList と違ってバッファをメモリに溜め込まないので大きなファイルを処理するのに向いています。
.NET 由来ではありますが Pascal の伝統的なファイル操作に似ている所もあって、実際に使ってみると意外と馴染みます。
基本的な TStreamReader / TStreamWriter の使い方
■ TStreamWriter の基本的な使い方
TStreamWriter の方から説明します。最も簡単な使い方は次のようになります。実行すると UTF-8 のテキストファイルが生成されます。
uses
..., Classes;
var
Writer: TStreamWriter;
begin
Writer := TStreamWriter.Create('hello.txt'); // 上書きモード
try
Writer.WriteLine('Hello,');
Writer.WriteLine('world.');
finally
Writer.Free;
end;
end;
TStreamWriter のコンストラクタ (パラメータとしてファイル名を受け付ける)
パラメータ | #1 | #2 | #3 | #4 |
---|---|---|---|---|
名前 | Filename | Append | Encoding | BufferSize |
型 | string | Boolean | TEncoding | Integer |
デフォルト | (なし) | False | TEncoding.UTF-8 | 4096 1 |
コンストラクタ Create()
の 2 番目のパラメータとして True を指定すると追記モードになります (デフォルトで False です)。
// 追記モード, UTF-8
Writer := TStreamWriter.Create('hello.txt', True);
コンストラクタ Create()
の 3 番目のパラメータとして TEncoding を渡して文字コードを指定する事もできます。
// 上書モード, UTF-8
Writer := TStreamWriter.Create('hello.txt', False, TEncoding.UTF8);
注意点ですが、上書きモード (正確にはストリームポインタがファイルの先頭にある状態) かつ Encoding パラメータに TEncoding.UTF8 が指定された場合には BOM が書き込まれます。つまり、次の 2 つは同等ではありません。
// 上書きモード, UTF-8 (BOM なし UTF-8)
Writer := TStreamWriter.Create('hello.txt');
// 上書きモード, UTF-8 (BOM あり UTF-8)
Writer := TStreamWriter.Create('hello.txt', False, TEncoding.UTF8);
ストリームの先頭へ移動すればこの問題を回避できます。
// 上書きモード, UTF-8 (BOM なし UTF-8)
Writer := TStreamWriter.Create('hello.txt');
// 上書きモード, UTF-8 (BOM あり UTF-8)
Writer := TStreamWriter.Create('hello.txt', False, TEncoding.UTF8);
Writer.BaseStream.Seek(0, TSeekOrigin.soBeginning); // ストリームの先頭に移動 (BOM を書き込まない)
TStreamWriter のプロパティ
プロパティ | 型 | デフォルト | 説明 |
---|---|---|---|
AutoFlush | Boolean | True | 自動でフラッシュ (ファイルへの書き出し) するか? |
BaseStream | TStream | 書き込みに使われている TStream | 書き込みに使われている TStream |
Encoding | TEncoding | TEncoding.UTF8 | 文字エンコーディング |
NewLine | string | sLineBreak | 改行文字 |
TStreamWriter のメソッド
メソッド | 説明 |
---|---|
Close() | TStreamWriter を閉じる。このメソッドはデストラクタで呼ばれ、明示的に呼んでも TStreamWriter のインスタンスは破棄されない |
Flush() | フラッシュ (ファイルへの書き出し) を行う。AutoFlush プロパティが True の場合には呼び出す必要がない |
Free() | TStreamWriter を破棄する。デストラクタで Close() が呼ばれる |
OwnStream() | 書き込みに使われている TStream のオーナーを TStreamWriter に設定する |
Write() | データを文字列として書き込む |
WriteLine() | データを行の終端文字で終わる文字列として書き込む |
■ TStreamReader の基本的な使い方
次は TStreamReader です。最も簡単な使い方は次のようになります。実行すると (BOM なし) UTF-8 のテキストファイルを読み込みます。
uses
..., Classes;
var
Reader: TStreamReader;
LineStr: string;
begin
Reader := TStreamReader.Create('hello.txt');
try
while Reader.EndOfStream do
begin
LineStr := Reader.ReadLine;
...
end;
finally
Reader.Free;
end;
end;
TStreamReader のコンストラクタ (パラメータとしてファイル名を受け付ける)
パラメータ | #1 | #2 |
---|---|---|
名前 | Filename | DetectBOM |
型 | string | Boolean |
デフォルト | (なし) |
パラメータ | #1 | #2 | #3 | #4 |
---|---|---|---|---|
名前 | Filename | Encoding | DetectBOM | BufferSize |
型 | string | TEncoding | Boolean | Integer |
デフォルト | (なし) | TEncoding.UTF-8 | False | 4096 1 |
コンストラクタ Create()
の DetectBOM パラメータに True を指定すると UTF-8 (BOM あり) を検出して読み込めます。つまり、次のような記述であれば、BOM の有無を気にせず UTF-8 形式のファイルを読み込めます。
// UTF-8, BOM 自動検出
Reader := TStreamReader.Create('hello.txt', True);
コンストラクタ Create()
の Encoding パラメータに TEncoding を指定し、DetectBOM パラメータに True を設定すると UTF-8 (BOM あり) だった場合には UTF-8 (BOM あり) で、それ以外の場合には指定された文字エンコーディングで読み込みます。
// 文字エンコーディング指定, BOM 自動検出
Reader := TStreamReader.Create('hello.txt', TEncoding.Default, True);
Delphi XE7〜10.2 Tokyo で、TEncoding を指定し、かつ DetectBOM を有効にしたコンストラクタを使って BOM あり UTF-8 ファイルを読み込むとエラーになる事があるようです。
根本的な解決方法はありませんが、問題が起こる環境では自前で BOM を判定すればいいだけなので、解っていれば対処は難しくないと思います。
TStreamReader のプロパティ
プロパティ | 型 | デフォルト | 説明 |
---|---|---|---|
BaseStream | TStream | 読み込みに使われている TStream | 読み込みに使われている TStream |
CurrentEncoding | TEncoding | – | 現在の文字エンコーディング |
EndOfStream | Boolean | – | ストリームの終端ならば True |
TStreamReader のメソッド
メソッド | 説明 |
---|---|
Close() | TStreamReader を閉じる。このメソッドはデストラクタで呼ばれ、明示的に呼んでも TStreamReader のインスタンスは破棄されない |
DiscardBufferedData() | バッファされているすべてのデータを破棄する |
Free() | TStreamReader を破棄する。デストラクタで Close() が呼ばれる |
OwnStream() 2 | 読み込みに使われている TStream のオーナーを TStreamReader に設定する |
Peek() | ストリームポインタを変更せずに、次の文字を取得する。ストリーム終端ならば -1 が返る |
Read() | ストリームポインタを変更し、次の文字を取得する。ストリーム終端ならば -1 が返る |
ReadBlock() | 一連の文字を読み込む |
ReadLine() | 1 行分の文字列を読み込む |
ReadToEnd() | 行末までの文字列を読み込む |
Rewind() 3 | バッファされているすべてのデータを破棄し、ストリームポインタをストリームの先頭へ移動する |
Rewind() は古いバージョンには存在しませんが、次のコードと同等です。
Reader.DiscardBufferedData;
Reader.BaseStream.Seek(0, TSeekOrigin.soBeginning);
■ コンストラクタに TStream を指定する場合
TStreamReader / TStreamWriter には TStream をパラメータとして受け付けるオーバーロードされたコンストラクタがあります。
TStreamWriter のコンストラクタ (パラメータとして TStream を受け付ける)
パラメータ | #1 | #2 | #3 |
---|---|---|---|
名前 | Stream | Encoding | BufferSize |
型 | TStream | TEncoding | Integer |
デフォルト | (なし) | TEncoding.UTF-8 | 4096 1 |
TStreamReader のコンストラクタ (パラメータとして TStream を受け付ける)
パラメータ | #1 | #2 |
---|---|---|
名前 | Stream | DetectBOM |
型 | TStream | Boolean |
デフォルト | (なし) |
パラメータ | #1 | #2 | #3 | #4 |
---|---|---|---|---|
名前 | Stream | Encoding | DetectBOM | BufferSize |
型 | TStream | TEncoding | Boolean | Integer |
デフォルト | (なし) | TEncoding.UTF-8 | False | 4096 1 |
パラメータに TStream が渡された場合、デフォルトでは TStreamReader / TStreamWriter を破棄しても TStream は破棄されません。
例えば IOUtils.TFile 4 を使ってファイルストリームを作って渡す場合には、ファイルストリームも破棄する必要があります。
Writer := TStreamWriter.Create(TFile.Create('hello.txt'));
try
Writer.WriteLine('Hello,');
Writer.WriteLine('world.');
finally
Writer.BaseStream.Free; // コンストラクタに渡された TStream を破棄
Writer.Free;
end;
コンストラクタに渡された TStream のオーナーを TStreamReader / TStreamWriter に変更すれば TStreamReader / TStreamWriter が破棄されたと同時に破棄されます 2。
Writer := TStreamWriter.Create(TFile.Create('hello.txt'));
try
Writer.OwnStream; // Stream のオーナーを TStreamWriter に
Writer.WriteLine('Hello,');
Writer.WriteLine('world.');
finally
Writer.Free; // デストラクタで Close() が呼ばれ、Stream が破棄される。
end;
■ TStreamReader / TStreamWriter を標準入出力に割り当てる (Windows)
次のコードでコンソールアプリケーションで TStreamReader / TStreamWriter を標準入出力に割り当てる事ができます。
uses
..., Classes, Windows;
var
Reader: TStreamReader;
Writer: TStreamWriter;
begin
Reader := TStreamReader.Create(THandleStream.Create(GetStdHandle(STD_INPUT_HANDLE)));
Writer := TStreamWriter.Create(THandleStream.Create(GetStdHandle(STD_OUTPUT_HANDLE)));
try
Writer.WriteLine('Hello,world.');
Reader.ReadLine;
finally
Reader.BaseStream.Free;
Reader.Free;
Writer.BaseStream.Free;
Writer.Free;
end;
end;
■ TStreamWriter の Write() / WriteLine()
TStreamWriter の Write() / WriteLine() には多くのオーバーライドされたメソッドが存在するため、文字列へと変換する機会はそうそうないかと思います。
// Write()
procedure Write(Value: Boolean); override;
procedure Write(Value: Char); override;
procedure Write(const Value: TCharArray); override;
procedure Write(Value: Double); override;
procedure Write(Value: Integer); override;
procedure Write(Value: Int64); override;
procedure Write(Value: TObject); override;
procedure Write(Value: Single); override;
procedure Write(const Value: string); override;
procedure Write(Value: Cardinal); override;
procedure Write(Value: UInt64); override;
procedure Write(Value: TCharArray; Index, Count: Integer); override;
// WriteLine()
procedure WriteLine; override;
procedure WriteLine(Value: Boolean); override;
procedure WriteLine(Value: Char); override;
procedure WriteLine(const Value: TCharArray); override;
procedure WriteLine(Value: Double); override;
procedure WriteLine(Value: Integer); override;
procedure WriteLine(Value: Int64); override;
procedure WriteLine(Value: TObject); override;
procedure WriteLine(Value: Single); override;
procedure WriteLine(const Value: string); override;
procedure WriteLine(Value: Cardinal); override;
procedure WriteLine(Value: UInt64); override;
procedure WriteLine(Value: TCharArray; Index, Count: Integer); override;
書式付き Write() / WriteLine()
Format()
と同じ書式文字列とオープン配列コンストラクタをパラメータとして渡せる Write() / WriteLine() メソッドが用意されています。
// Write()
procedure Write(const Format: string; Args: array of const); override;
// WriteLine()
procedure WriteLine(const Format: string; Args: array of const); override;
標準手続きの Write() / Writeln() の可変パラメータと似たような記述が可能となっています。
// Write()
Writer.Write('%s_%.3d.txt', ['LOG', rev]);
// WriteLine()
Writer.WriteLine('%.4d: %s', [Id, Name]);
See also:
■ TStrem.Seek() によるストリームポインタの移動
関連しますが、TStrem.Seek() の 2 番目のパラメータ (Origin) には TSeekOrigin 列挙型を指定して、巨大なファイルでも問題なく移動できるようにすべきです。
具体的には soBeginning
などではなく、TSeekOrigin.soBeginning
のように明示します。
Reader.BaseStream.Seek(FileSize, soBeginning); // NG
Reader.BaseStream.Seek(FileSize, TSeekOrigin.soBeginning); // OK
TSeekOrigin.
で修飾すると常に Int64 の方の Seek() が選択されるからです。
function Seek(Offset: Longint; Origin: Word): Longint; overload; virtual;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; virtual;
See also:
TStringList が便利だからと、なんでもかんでも TStringList でやっているとパフォーマンスの低下につながる事があります。
ちょっとクセがあったりもしますが、テキストファイルの読み書きを TStreamReader / TStreamWriter に置き換えて高速化が行えないか検討してみてはいかがでしょうか?
余談
本記事のコードは可能な限り古いバージョンでも通るように記述しましたが、Delphi 10.3 以降のインライン変数宣言と型推論を使えばもっと簡潔に書けます。
最も簡単な使い方は次のようになります
まぁ、with 文を使えばもっと簡単になりますけれど。
TStreamWriter
with TStreamWriter.Create('hello.txt') do
try
WriteLine('Hello,');
WriteLine('world.');
finally
Free;
end;
TStreamReader
with TStreamReader.Create('hello.txt', True) do
try
while EndOfStream do
begin
var LineStr := ReadLine;
...
end;
finally
Free;
end;
See also: