テキストファイルに確実に書き込む

アプリケーションのログ記録をテキストファイルに書き込むときは StreamWriter を使うことが多いと思います。ただ、これは Close(); 時に書き込みが完了するようで、アプリケーションが異常終了したときなど Close(); がうまく呼ばれなかったときは、0バイトのテキストファイルが出来るだけです。

Close(); がコールされなくても、なんとかそこまで書き込んでいた情報がテキストファイルに残ってほしいですよね。

ストリームに Write(); や WriteLine(); した直後に flush(); すれば、Close(); が呼ばれなくても記録は残ります。

テキストファイルにデータを書き込むときは using() を使って局所的にオープン・クローズを実施するのが定石ですので、下記のようなコードを書いてはいけません。下記のコードはあくまでも flush(); の効き目を実感してもらうためのものです。ファイルパスは自分の好きな場所を指定してください。

using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{

		const String FILEPATH_0 = "c:/tmp/test0.txt";
		const String FILEPATH_1 = "c:/tmp/test1.txt";

		StreamWriter Sw0 = null;
		StreamWriter Sw1 = null;

		const String STRFMT = "yyyy/MM/dd_HH:mm:ss";

		public Form1()
		{
			InitializeComponent();
		}


		private void Form1_Load( object sender, EventArgs e )
		{
			Encoding enc = new UTF8Encoding( false );

			Sw0 = new StreamWriter( FILEPATH_0, true, enc );
			Sw1 = new StreamWriter( FILEPATH_1, true, enc );
		}

		private void Form1_FormClosing( object sender, FormClosingEventArgs e )
		{

			// あえて両方ともストリームライタをクローズしない.

			// コメントアウト.
			//if ( Sw0 != null )
			//{
			//	Sw0.Close();
			//	Sw0.Dispose();
			//	Sw0 = null;
			//}

			// コメントアウト.
			//if ( Sw1 != null )
			//{
			//	Sw1.Close();
			//	Sw1.Dispose();
			//	Sw1 = null;
			//}

		}

		private void button1_Click( object sender, EventArgs e )
		{

			// 現在時刻の文字列を取得する.
			String s = DateTime.Now.ToString( STRFMT );
			Sw0.WriteLine( s );

		}

		private void button2_Click( object sender, EventArgs e )
		{

			// 現在時刻の文字列を取得する.
			String s = DateTime.Now.ToString( STRFMT );
			Sw1.WriteLine( s );

			// 確実に書き込む.
 			Sw1.Flush();

		}
	}
}

実際にストリームライターを使う定石は下記のコードです。

		private void button3_Click( object sender, EventArgs e )
		{

			const String FILEPATH = "c:/tmp/test3.txt";
			Encoding enc = new UTF8Encoding( false );

			using ( StreamWriter sw = new StreamWriter( FILEPATH, true, enc ))
			{
				String s = DateTime.Now.ToString( STRFMT );
				sw.WriteLine( s );

				// これは好みで入れたり、入れなかったり.
				// 直後に Dispose() されるので、flush()、Close()は実施されるから.
				sw.Flush();

				// using で囲んでいれば Close(); は不要.
				// using で囲んでおけば Disopose(); も不要.
			}

		}

Write() や WriteLine() したら、その直後に何も書かなくても flush() させるには、AutoFlush プロパティを true にします。デフォルトは false です。

		private void button4_Click( object sender, EventArgs e )
		{

			const String FILEPATH = "c:/tmp/test4.txt";
			Encoding enc = new UTF8Encoding( false );

			using ( StreamWriter sw = new StreamWriter( FILEPATH, true, enc ))
			{

				// オートフラッシュをtrueにしておけば WriteLine() 直後に自動的に flush() がコールされる.
				sw.AutoFlush = true;

				String s = DateTime.Now.ToString( STRFMT );
				sw.WriteLine( s );

			}

		}

なぜデフォルトが false なのかというと、大量の文字列データが書き込まれた場合ファイルI/Oが忙しくなってアプリケーション全体の実行速度を阻害してしまうからです。

ちょっとした文字列を書き込むぐらいだったら速度の阻害は感じられませんが、10MB程度の文字列を WriteLine() してから flush() すればモタモタした感じが体感できると思います。

高速なSSDでディスクキャッシュが巨大だと感じられないかもしれません。外部記憶のRead/Writeの高速化に感謝(笑)。