C#でファイルをキャッシュするプログラム(Dictionary-TSV)

C# コンピュータ
C#

キャッシュ用のディレクトリにMemoryStreamの内容をファイルとして書き出し、次回以降同じファイルの読み込みはキャッシュファイルを読み込むようにしています。

キャッシュファイルの管理は前回の記事ではSqliteを使いましたが、今回Dictionaryの内容をTSVファイルで保存するようにしています。

ソースコード

ファイル名:FileCacheEntity.cs

using System.ComponentModel.DataAnnotations;

class FileCacheEntity
{
    public string Path { get; set; } = "";
    public long Length { get; set; } = -1;
    public DateTime LastUpdate { get; set; }
    public string GUID { get; set; }

    public FileCacheEntity()
    {
        LastUpdate = DateTime.Now;
        GUID = Guid.NewGuid().ToString("N");
    }

    override public string ToString()
    {
        string result = "";

        result += Path + "\t";
        result += Length.ToString() + "\t";
        result += LastUpdate.ToString("yyyy/MM/dd HH:mm:ss") + "\t";
        result += GUID;

        return result;
    }
    public void Parse(string str)
    {
        string[] array = str.Split("\t");

        Path = array[0];
        Length = long.Parse(array[1]);
        LastUpdate = DateTime.Parse(array[2]);
        GUID = array[3];

        /*
            Console.WriteLine($"Path: {Path}");
            Console.WriteLine($"Length: {Length}");
            Console.WriteLine($"LastUpdate: {LastUpdate}");
            Console.WriteLine($"GUID: {GUID}");
        */
    }
}

ファイル名:FileCacheManager.cs

using Microsoft.VisualBasic;

class FileCacheManager : IDisposable
{
    string _tsvPath;
    string _cacheDir;
    Dictionary<string, FileCacheEntity> _dic;

    public FileCacheManager(string tsvPath = @".\FileCache01.tsv", string cacheDir = @".\.cache")
    {
        _tsvPath = tsvPath;
        _cacheDir = cacheDir;

        if (!Directory.Exists(_cacheDir))
            Directory.CreateDirectory(_cacheDir);

        _dic = new();

        if (!File.Exists(_tsvPath)) return;

        using (StreamReader reader = File.OpenText(_tsvPath))
        {
            while(!reader.EndOfStream)
            {
                string? line = reader.ReadLine();
                if (line is null) continue;
                FileCacheEntity fce = new();
                fce.Parse(line);
                _dic[fce.Path] = fce;
            }
        }
    }
    public void Dispose()
    {
        if (File.Exists(_tsvPath))
            File.Delete(_tsvPath);

        using (StreamWriter writer = new (_tsvPath))
        {
            foreach(FileCacheEntity fec in _dic.Values)
            {
                writer.WriteLine(fec.ToString());
            }
        }   
    }
    public MemoryStream Get(string path)
    {
        MemoryStream ms = new();
        if (_dic.ContainsKey(path))
        {
            var e = _dic[path];
            string cachePath = Path.Join(_cacheDir, e.GUID);
            if (File.Exists(cachePath))
            {
                using FileStream fs = new (cachePath, FileMode.Open, FileAccess.Read);
                fs.CopyTo(ms);
                ms.Seek(0, SeekOrigin.Begin);
                return ms;
            }
        }
        return ms;
    }
    public void Set(string path, MemoryStream ms)
    {
        FileInfo fi = new (path);
        FileCacheEntity fce = new()
        {
            Path = path,
            Length = fi.Length,
            LastUpdate = fi.LastWriteTime,
        };

        string cachePath = Path.Join(_cacheDir, fce.GUID);
        if (File.Exists(cachePath))
            File.Delete(cachePath);
        ms.Seek(0, SeekOrigin.Begin);
        using FileStream fs = new (cachePath, FileMode.CreateNew, FileAccess.Write);
        ms.CopyTo(fs);

        _dic[path] = fce;
    }
}

ファイル名:Program.cs

using System.Diagnostics;

class Program
{
    public static void Main()
    {
        const string targetFile = @"H:\Pictures\202410081107.PNG";

        using var fcm = new FileCacheManager();

        Stopwatch sw = new();
        sw.Start();
        using MemoryStream ms = fcm.Get(targetFile);

        if (ms.Length > 0)
        {
            sw.Stop();
            Console.WriteLine($"キャッシュに有り サイズ:{ms.Length}Byte 時間:{sw.ElapsedMilliseconds}ms");
        }
        else
        {
            using FileStream fs = new (targetFile, FileMode.Open, FileAccess.Read);
            ms.Seek(0, SeekOrigin.Begin);
            fs.CopyTo(ms);
            fcm.Set(targetFile, ms);
            sw.Stop();
            Console.WriteLine($"キャッシュに無し サイズ:{ms.Length}Byte 時間:{sw.ElapsedMilliseconds}ms");
        }

        /*
        FileInfo fi = new (targetFile);

        FileCacheEntity fce = new FileCacheEntity()
        {
            Path = targetFile,
            LastUpdate = fi.LastWriteTime,
            Length = fi.Length,
        };

        string str = fce.ToString();
        Console.WriteLine(str);
        fce.Parse(str);
        */
    }
}

結果

初回

キャッシュに無し サイズ:6955Byte 時間:32ms

2回目

キャッシュに有り サイズ:6955Byte 時間:1ms

TSVファイルに保存はFileCacheMangerのオブジェクトをDispose()で実行しているのでusingで確実にDispose()するとよいでしょう。

前回のEntityFramework(Sqlite)を使う方法より速いですが、データ量が増えると遅くなるはずです。

コメント