C#で最大容量つきリングバッファーを実装する

リングバッファーは、FIFO(ファーストIN, ファーストOUT)つまり先に入れたものが、取り出すときは先に出ていく、Queueと同じような構造を持っています。

リングバッファーって自分のイメージでは、有限のサイズのリングの大きさ(つまり入れられる最大の量)が決まっていて、バッファーのキャパシティの上限を超過して要素が挿入されると先頭の要素は消えてしまうイメージがあります。

C#にもQueueがクラスがあって、内部実装はリングバッファー(循環バッファ?)で実装されているとのことですが、このQueueクラスは要素を末尾に追加したら、した分だけリングが大きくなっちゃうので、PCのリソースが許す限り大きいリングが出来上がってしまいます。(まぁQueueですからね…

なので、大変乱暴ですが、最初に決めた上限を超過して要素を追加すると先頭データが消えるリングバッファーを自作しようと思います。

最大容量付きリングバッファー

大したことはないのですが、コンストラクターでバッファの最大容量を指定し、データの追加をEnqueue, データの取り出しをDequeue, データを削除しないで先頭の取り出しをPeek, バッファの内容の列挙としてIEnumerableを実装しています。

public class FixedRingBuffer<T> : IEnumerable<T>
{
    private Queue<T> _queue;

    public int Count => this._queue.Count;

    public int MaxCapacity { get; private set; }

    public FixedRingBuffer(int maxCapacity)
    {
        this.MaxCapacity = maxCapacity;
        this._queue = new Queue<T>(maxCapacity);
    }

    public void Enqueue(T item)
    {
        this._queue.Enqueue(item);

        if (this._queue.Count > this.MaxCapacity)
        {
            T removed = this.Dequeue();
            Console.WriteLine($"キャパシティを超えているため" +
                $"バッファの先頭データ破棄しました。{removed.ToString()}");
        }
    }

    public T Dequeue() => this._queue.Dequeue();

    public T Peek() => this._queue.Peek();

    public IEnumerator<T> GetEnumerator() => this._queue.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => this._queue.GetEnumerator();
}

使い方

上記コードを以下のように最大容量5でインスタンス化し、10件のデータを追加後、バッファの内容をすべて取り出します。

internal class Program
{
    public static void Main(string[] args)
    {
        var ringBuffer = new FixedRingBuffer<int>(5);
        
        for(int i = 0; i < 10; i++)
        {
            Console.WriteLine($"データを追加 {i}");
            ringBuffer.Enqueue(i);
        }

        Console.Write("現在のQueueの内容 => ");

        foreach (int item in ringBuffer)
        {
            Console.Write($"{item}, ");
        }

        Console.WriteLine("");

        while(ringBuffer.Count > 0)
        {
            int item = ringBuffer.Dequeue();
            Console.WriteLine($"データの取り出し {item}");
        }
    }
}

上記コードを実行すると以下がコンソールに出力されます。

> データを追加 0
> データを追加 1
> データを追加 2
> データを追加 3
> データを追加 4
> データを追加 5
> キャパシティを超えているためバッファの先頭データ破棄しました。0
> データを追加 6
> キャパシティを超えているためバッファの先頭データ破棄しました。1
> データを追加 7
> キャパシティを超えているためバッファの先頭データ破棄しました。2
> データを追加 8
> キャパシティを超えているためバッファの先頭データ破棄しました。3
> データを追加 9
> キャパシティを超えているためバッファの先頭データ破棄しました。4
> 現在のQueueの内容 => 5, 6, 7, 8, 9,
> データの取り出し 5
> データの取り出し 6
> データの取り出し 7
> データの取り出し 8
> データの取り出し 9