ディスクキャッシュに影響されずにビットマップファイルを保存する

ビットマップファイルを保存するときに数メガバイト程度のサイズだと特に気になる問題ではありませんが、下記のような場合では問題が発生するときがあります。

・複数のプロセス(EXE)から同一のビットマップファイルをアクセスする場合
・やたらと大きなビットマップファイルの場合

ディスクキャッシュの大きなシステムで運用している場合などは、Bitmap クラスの Save メソッドで第1引数がファイルパスのものを使うと処理が終わっても実際はディスクに保存されておらず、それを別の EXE から読みだせなかったりします。

明示的にディスクキャッシュの影響を排除して保存するには、同じ Save メソッドで第1引数がファイルストリームのものを利用します。

具体的にはファイルストリームのコンストラクタ引数で FileOptions.WriteThrough とし、ファイルストリームを Flush() してやります。ただしディスクキャッシュの影響がなくなるので保存実行時間は遅くなります。

Form1 の上に button1 と button2 を配置して下記のコードを実行して違いを実感し比較検討してください。

button1 が Save の第1引数をファイルパス文字列にして保存する方法です。
button2 が Save の第1引数をファイルストリームにして保存する方法です。

FileStream のコンストラクタ引数における、バッファサイズ BUFFER_SIZE_WRITING_DISK は、実行環境や保存するファイルのサイズによって増やしてみると若干高速化する場合があります。

( わたくしの環境では button1 で約300msec、button2 で約1500msecでした )

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{

		const int IMG_W =  8192;
		const int IMG_H = 16384;

		const String FILEPATH_01 = "c:/tmp/test1.bmp";
		const String FILEPATH_02 = "c:/tmp/test2.bmp";

		PixelFormat PixFmt = PixelFormat.Format24bppRgb;
		ImageFormat ImgFmt = ImageFormat.Bmp;

		public Form1()
		{
			InitializeComponent();
		}

		private void button1_Click( object sender, EventArgs e )
		{

			Stopwatch sw = new Stopwatch();
			sw.Start();

			// ファイルパスを与えて保存する場合.
			using ( Bitmap bmp = new Bitmap( IMG_W, IMG_H, PixFmt ))
			{
				// 保存する.
				bmp.Save( FILEPATH_01, ImgFmt );
			}

			long msec = sw.ElapsedMilliseconds;
			String strmsg = String.Format( "{0} x {1}: {2}msec", IMG_W, IMG_H, msec );
			MessageBox.Show( strmsg );

		}

		private void button2_Click( object sender, EventArgs e )
		{

			Stopwatch sw = new Stopwatch();
			sw.Start();

			// ファイルストリームを明示利用して保存する場合.
			using ( Bitmap bmp = new Bitmap( IMG_W, IMG_H, PixFmt ))
			{
				FileMode fm = FileMode.Create;
				FileAccess fa = FileAccess.Write;
				FileShare fs = FileShare.None;
				const int BUFFER_SIZE_WRITING_DISK = 4096;
				FileOptions fo = FileOptions.WriteThrough;
				using ( FileStream file_stream = new FileStream( FILEPATH_02, fm, fa, fs, BUFFER_SIZE_WRITING_DISK, fo ) )
				{
					// 保存する.
					bmp.Save( file_stream, ImgFmt );
					file_stream.Flush();
				}
			}

			long msec = sw.ElapsedMilliseconds;
			String strmsg = String.Format( "{0} x {1}: {2}msec", IMG_W, IMG_H, msec );
			MessageBox.Show( strmsg );

		}
	}
}