web-dev-qa-db-ja.com

JavaまたはC#で効率的な循環バッファーをどのようにコーディングしますか?

固定サイズ circular buffer を実装する単純なクラスが必要です。効率的で、目にやさしく、一般的に型付けされている必要があります。

今のところ、 MT-capable である必要はありません。後でいつでもロックを追加できます。どのような場合でも高い同時実行性はありません。

メソッドは.Add()で、.List()である必要があります。ここですべてのエントリを取得します。考え直して、検索はインデクサーを介して行われるべきだと思います。いつでもindexでバッファ内の要素を取得できるようにしたいと思うでしょう。ただし、循環バッファーがいっぱいになりロールオーバーするため、ある瞬間から次のElement [n]までは異なる場合があることに留意してください。これはstackではなく、循環バッファーです。

overflow」について:内部的にはアイテムを保持する配列があり、時間の経過とともにheadバッファーのおよびtailは、その固定配列を中心に回転します。しかし、それはユーザーから見えないはずです。外部で検出可能な「オーバーフロー」イベントまたは動作はありません。

これは学校の割り当てではありません。最も一般的には、 MRUキャッシュ または固定サイズのトランザクションログまたはイベントログに使用されます。

43
Cheeso

Tの配列、ヘッドポインターとテールポインターを使用し、メソッドを追加および取得します。

好き:(バグハンティングはユーザーに任されています)

// Hijack these for simplicity
import Java.nio.BufferOverflowException;
import Java.nio.BufferUnderflowException;

public class CircularBuffer<T> {

  private T[] buffer;

  private int tail;

  private int head;

  @SuppressWarnings("unchecked")
  public CircularBuffer(int n) {
    buffer = (T[]) new Object[n];
    tail = 0;
    head = 0;
  }

  public void add(T toAdd) {
    if (head != (tail - 1)) {
        buffer[head++] = toAdd;
    } else {
        throw new BufferOverflowException();
    }
    head = head % buffer.length;
  }

  public T get() {
    T t = null;
    int adjTail = tail > head ? tail - buffer.length : tail;
    if (adjTail < head) {
        t = (T) buffer[tail++];
        tail = tail % buffer.length;
    } else {
        throw new BufferUnderflowException();
    }
    return t;
  }

  public String toString() {
    return "CircularBuffer(size=" + buffer.length + ", head=" + head + ", tail=" + tail + ")";
  }

  public static void main(String[] args) {
    CircularBuffer<String> b = new CircularBuffer<String>(3);
    for (int i = 0; i < 10; i++) {
        System.out.println("Start: " + b);
        b.add("One");
        System.out.println("One: " + b);
        b.add("Two");
        System.out.println("Two: " + b);
        System.out.println("Got '" + b.get() + "', now " + b);

        b.add("Three");
        System.out.println("Three: " + b);
        // Test Overflow
        // b.add("Four");
        // System.out.println("Four: " + b);

        System.out.println("Got '" + b.get() + "', now " + b);
        System.out.println("Got '" + b.get() + "', now " + b);
        // Test Underflow
        // System.out.println("Got '" + b.get() + "', now " + b);

        // Back to start, let's shift on one
        b.add("Foo");
        b.get();
    }
  }
}
22
JeeBee

これは、Javaで効率的な循環バッファーを((または実行した))方法です。それは単純な配列に支えられています。私の特定のユースケースでは、高い同時スループットが必要だったため、インデックスの割り当てにCASを使用しました。次に、バッファー全体のCASコピーを含む信頼できるコピーのメカニズムを作成しました。 短い記事 で詳しく説明されているケースでこれを使用しました。

import Java.util.concurrent.atomic.AtomicLong;
import Java.lang.reflect.Array;

/**
 * A circular array buffer with a copy-and-swap cursor.
 *
 * <p>This class provides an list of T objects who's size is <em>unstable</em>.
 * It's intended for capturing data where the frequency of sampling greatly
 * outweighs the frequency of inspection (for instance, monitoring).</p>
 *
 * <p>This object keeps in memory a fixed size buffer which is used for
 * capturing objects.  It copies the objects to a snapshot array which may be
 * worked with.  The size of the snapshot array will vary based on the
 * stability of the array during the copy operation.</p>
 *
 * <p>Adding buffer to the buffer is <em>O(1)</em>, and lockless.  Taking a
 * stable copy of the sample is <em>O(n)</em>.</p>
 */
public class ConcurrentCircularBuffer <T> {
    private final AtomicLong cursor = new AtomicLong();
    private final T[]      buffer;
    private final Class<T> type;

    /**
     * Create a new concurrent circular buffer.
     *
     * @param type The type of the array.  This is captured for the same reason
     * it's required by {@link Java.util.List.toArray()}.
     *
     * @param bufferSize The size of the buffer.
     *
     * @throws IllegalArgumentException if the bufferSize is a non-positive
     * value.
     */
    public ConcurrentCircularBuffer (final Class <T> type, 
                                     final int bufferSize) 
    {
        if (bufferSize < 1) {
            throw new IllegalArgumentException(
                "Buffer size must be a positive value"
                );
        }

        this.type    = type;
        this.buffer = (T[]) new Object [ bufferSize ];
    }

    /**
     * Add a new object to this buffer.
     *
     * <p>Add a new object to the cursor-point of the buffer.</p>
     *
     * @param sample The object to add.
     */
    public void add (T sample) {
        buffer[(int) (cursor.getAndIncrement() % buffer.length)] = sample;
    }

    /**
     * Return a stable snapshot of the buffer.
     *
     * <p>Capture a stable snapshot of the buffer as an array.  The snapshot
     * may not be the same length as the buffer, any objects which were
     * unstable during the copy will be factored out.</p>
     * 
     * @return An array snapshot of the buffer.
     */
    public T[] snapshot () {
        T[] snapshots = (T[]) new Object [ buffer.length ];

        /* Determine the size of the snapshot by the number of affected
         * records.  Trim the size of the snapshot by the number of records
         * which are considered to be unstable during the copy (the amount the
         * cursor may have moved while the copy took place).
         *
         * If the cursor eliminated the sample (if the sample size is so small
         * compared to the rate of mutation that it did a full-wrap during the
         * copy) then just treat the buffer as though the cursor is
         * buffer.length - 1 and it was not changed during copy (this is
         * unlikley, but it should typically provide fairly stable results).
         */
        long before = cursor.get();

        /* If the cursor hasn't yet moved, skip the copying and simply return a
         * zero-length array.
         */
        if (before == 0) {
            return (T[]) Array.newInstance(type, 0);
        }

        System.arraycopy(buffer, 0, snapshots, 0, buffer.length);

        long after          = cursor.get();
        int  size           = buffer.length - (int) (after - before);
        long snapshotCursor = before - 1;

        /* Highly unlikely, but the entire buffer was replaced while we
         * waited...so just return a zero length array, since we can't get a
         * stable snapshot...
         */
        if (size <= 0) {
            return (T[]) Array.newInstance(type, 0);
        }

        long start = snapshotCursor - (size - 1);
        long end   = snapshotCursor;

        if (snapshotCursor < snapshots.length) {
            size   = (int) snapshotCursor + 1;
            start  = 0;
        }

        /* Copy the sample snapshot to a new array the size of our stable
         * snapshot area.
         */
        T[] result = (T[]) Array.newInstance(type, size);

        int startOfCopy = (int) (start % snapshots.length);
        int endOfCopy   = (int) (end   % snapshots.length);

        /* If the buffer space wraps the physical end of the array, use two
         * copies to construct the new array.
         */
        if (startOfCopy > endOfCopy) {
            System.arraycopy(snapshots, startOfCopy,
                             result, 0, 
                             snapshots.length - startOfCopy);
            System.arraycopy(snapshots, 0,
                             result, (snapshots.length - startOfCopy),
                             endOfCopy + 1);
        }
        else {
            /* Otherwise it's a single continuous segment, copy the whole thing
             * into the result.
             */
            System.arraycopy(snapshots, startOfCopy,
                             result, 0, endOfCopy - startOfCopy + 1);
        }

        return (T[]) result;
    }

    /**
     * Get a stable snapshot of the complete buffer.
     *
     * <p>This operation fetches a snapshot of the buffer using the algorithm
     * defined in {@link snapshot()}.  If there was concurrent modification of
     * the buffer during the copy, however, it will retry until a full stable
     * snapshot of the buffer was acquired.</p>
     *
     * <p><em>Note, for very busy buffers on large symmetric multiprocessing
     * machines and supercomputers running data processing intensive
     * applications, this operation has the potential of being fairly
     * expensive.  In practice on commodity hardware, dualcore processors and
     * non-processing intensive systems (such as web services) it very rarely
     * retries.</em></p>
     *
     * @return A full copy of the internal buffer.
     */
    public T[] completeSnapshot () {
        T[] snapshot = snapshot();

        /* Try again until we get a snapshot that's the same size as the
         * buffer...  This is very often a single iteration, but it depends on
         * how busy the system is.
         */
        while (snapshot.length != buffer.length) {
            snapshot = snapshot();
        }

        return snapshot;
    }

    /**
     * The size of this buffer.
     */
    public int size () {
        return buffer.length;
    }
}
7
Scott S. McCoy

すぐに使用できる Java用のCircularArrayList実装 は、本番コードで使用しています。 Java推奨の方法でAbstractListをオーバーライドすることにより、Java Collections Framework(ジェネリック要素タイプ、subList、反復など)の標準リスト実装に期待されるすべての機能をサポートします。

次の呼び出しは、O(1)で完了します。

  • add(item)-リストの最後に追加します
  • remove(0)-リストの先頭から削除します
  • get(i)-リスト内のランダムな要素を取得します
5
Museful

Javaの ArrayDeque を使用します

4

ArrayBlockingQueue または他の事前構築されたQueue実装のいずれかを使用します。ニーズに応じて。ごくまれに、そのようなデータ構造を自分で実装する必要はありません(学校の割り当てでない限り)。

編集:「インデックスによってバッファ内の任意の要素を取得する」という要件を追加したので、独自のクラスを実装する必要があると思います( google-collections または他のライブラリが提供する場合を除く) 。 JeeBeeの例が示すように、循環バッファーの実装は非常に簡単です。また、ArrayBlockingQueueのソースコードを確認することもできます。コードは非常にきれいで、ロックメソッドと不要なメソッドを削除し、インデックスでアクセスするためのメソッドを追加するだけです。

4
Esko Luontola

他の誰かの実装を使用するだけです:

パワーコレクションDeque<T> は、循環バッファによって実装されます。

電力収集ライブラリは不完全ですが、Dequeは循環バッファを拡張するのに完全に受け入れられます。

展開を望まず、上書きを希望することを示しているため、上書きするコードをかなり簡単に変更できます。これには、論理的に隣接しているポインタのチェックを削除し、とにかく書き込むだけです。同時に、プライベートバッファを読み取り専用にすることもできます。

2
ShuggyCoUk

Javaパースペクティブでこの質問に答えたいと思います。

Javaで循環バッファーを実装するには、おそらく次の3つのものが必要です:循環バッファークラス、ジェネリック、およびそれに対するいくつかの操作(必要な操作とこれらの操作の内部メカニズムを知るには、 最初は循環バッファーのwiki )。

第二に、バッファが満杯か空かという判断は非常に慎重に扱われるべきです。ここでは、完全/空の判断のための2つの本能的な解決策を示します。ソリューション1では、バッファーの現在のサイズとバッファーの最大サイズの両方を格納するために、2つの整数バリアントを作成する必要があります。明らかに、現在のサイズが最大サイズに等しい場合、バッファーはいっぱいです。

別のソリューションでは、最後の1つのストレージをアイドル状態に設定します(サイズ7の循環バッファーの場合、ストレージをアイドル状態で7に設定します)。これによれば、式(rp+1)%MAXSIZE == fp;が満たされたときにバッファーがいっぱいであると判断できます。

より明確にするために、ここではJava言語。

import Java.nio.BufferOverflowException;
import Java.nio.BufferUnderflowException;        

public class CircularBuffer<T> {
    private int front;
    private int rear;
    private int currentSize;
    private int maxSize;
    private T[] buffer;

    public CircularBuffer(int n) {
        buffer = (T[]) new Object[n];
        front = 0;
        rear = 0;
        currentSize = 0;
        maxSize = n;
    }

    public void Push(T e) {
        if (!isFull()) {
            buffer[rear] = e;
            currentSize++;
            rear = (rear + 1) % maxSize;
        } else throw new BufferOverflowException();
    }

    public T pop() {
        if (!isEmpty()) {
            T temp = buffer[front];
            buffer[front] = null;
            front = (front + 1) % maxSize;
            currentSize--;
            return temp;
        } else throw new BufferUnderflowException();
    }

    public T peekFirst() {
        if (!isEmpty()) {
            return buffer[front];
        } else  return null;
    }

    public T peekLast() {
        if (!isEmpty()) {
            return buffer[rear - 1];
        } else return null;
    }

    public int size() {
        return currentSize;
    }

    public boolean isEmpty() {
        if (currentSize == 0) {
            return true;
        } else return false;
    }

    public boolean isFull() {
        if (currentSize == maxSize) {
            return true;
        } else return false;
    }

    public boolean clean() { 
        front = 0;          
        rear = 0;
        while (rear != 0) {
            buffer[rear] = null;
            rear = (rear + 1) % maxSize;
        }   
        return true;
    }

    public static void main(String[] args) {
        CircularBuffer<Integer> buff = new CircularBuffer<>(7);
        buff.Push(0);
        buff.Push(1);
        buff.Push(2);
        System.out.println(buff.size());
        System.out.println("The head element is: " + buff.pop());
        System.out.println("Size should be twoo: " + buff.size());
        System.out.println("The last element is one: " + buff.peekLast());
        System.out.println("Size should be two: " + buff.size());
        buff.clean();
        System.out.println("Size should be zero: " + buff.size());

    }
}
1
Jingyu Xie

Guava 15では、EvictingQueueを導入しました。これは、フルに要素を追加しようとするときに、キューの先頭から要素を自動的に排除(削除)する非ブロッキングのキューです。キュー。これは、新しい要素がいっぱいになるとブロックまたは拒否する従来の境界キューとは異なります。

これはあなたのニーズに合うはずで、ArrayDequeを直接使用するよりもはるかにシンプルなインターフェースを持っているように思えます(ただし、内部で使用します!)。

詳細については、 こちら をご覧ください。

1

System.Collections.Generic.Queue-内部の単純な循環バッファーです(T []のように、頭と尻尾で JeeBeeのサンプル のように)。

1
Zakus
0
Ray Tayek

以下は、私自身が使用するためにコーディングした実装ですが、それは役に立つかもしれません。

バッファーには、アイテムの最大固定セットが含まれます。セットは円形で、古いアイテムは自動的に削除されます。呼び出し元は、絶対増分インデックス(長い)によってアイテムの末尾を取得できますが、アイテムが時間的に離れた呼び出し間で失われた可能性があります。このクラスは完全にスレッドセーフです。

public sealed class ConcurrentCircularBuffer<T> : ICollection<T>
{
    private T[] _items;
    private int _index;
    private bool _full;

    public ConcurrentCircularBuffer(int capacity)
    {
        if (capacity <= 1) // need at least two items
            throw new ArgumentException(null, "capacity");

        Capacity = capacity;
        _items = new T[capacity];
    }

    public int Capacity { get; private set; }
    public long TotalCount { get; private set; }

    public int Count
    {
        get
        {
            lock (SyncObject) // full & _index need to be in sync
            {
                return _full ? Capacity : _index;
            }
        }
    }

    public void AddRange(IEnumerable<T> items)
    {
        if (items == null)
            return;

        lock (SyncObject)
        {
            foreach (var item in items)
            {
                AddWithLock(item);
            }
        }
    }

    private void AddWithLock(T item)
    {
        _items[_index] = item;
        _index++;
        if (_index == Capacity)
        {
            _full = true;
            _index = 0;
        }
        TotalCount++;
    }

    public void Add(T item)
    {
        lock (SyncObject)
        {
            AddWithLock(item);
        }
    }

    public void Clear()
    {
        lock (SyncObject)
        {
            _items = new T[Capacity];
            _index = 0;
            _full = false;
            TotalCount = 0;
        }
    }

    // this gives raw access to the underlying buffer. not sure I should keep that
    public T this[int index]
    {
        get
        {
            return _items[index];
        }
    }

    public T[] GetTail(long startIndex)
    {
        long lostCount;
        return GetTail(startIndex, out lostCount);
    }

    public T[] GetTail(long startIndex, out long lostCount)
    {
        if (startIndex < 0 || startIndex >= TotalCount)
            throw new ArgumentOutOfRangeException("startIndex");

        T[] array = ToArray();
        lostCount = (TotalCount - Count) - startIndex;
        if (lostCount >= 0)
            return array;

        lostCount = 0;

        // this maybe could optimized to not allocate the initial array
        // but in multi-threading environment, I suppose this is arguable (and more difficult).
        T[] chunk = new T[TotalCount - startIndex];
        Array.Copy(array, array.Length - (TotalCount - startIndex), chunk, 0, chunk.Length);
        return chunk;
    }

    public T[] ToArray()
    {
        lock (SyncObject)
        {
            T[] items = new T[_full ? Capacity : _index];
            if (_full)
            {
                if (_index == 0)
                {
                    Array.Copy(_items, items, Capacity);
                }
                else
                {
                    Array.Copy(_items, _index, items, 0, Capacity - _index);
                    Array.Copy(_items, 0, items, Capacity - _index, _index);
                }
            }
            else if (_index > 0)
            {
                Array.Copy(_items, items, _index);
            }
            return items;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ToArray().AsEnumerable().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    bool ICollection<T>.Contains(T item)
    {
        return _items.Contains(item);
    }

    void ICollection<T>.CopyTo(T[] array, int arrayIndex)
    {
        if (array == null)
            throw new ArgumentNullException("array");

        if (array.Rank != 1)
            throw new ArgumentException(null, "array");

        if (arrayIndex < 0)
            throw new ArgumentOutOfRangeException("arrayIndex");

        if ((array.Length - arrayIndex) < Count)
            throw new ArgumentException(null, "array");

        T[] thisArray = ToArray();
        Array.Copy(thisArray, 0, array, arrayIndex, thisArray.Length);
    }

    bool ICollection<T>.IsReadOnly
    {
        get
        {
            return false;
        }
    }

    bool ICollection<T>.Remove(T item)
    {
        return false;
    }

    private static object _syncObject;
    private static object SyncObject
    {
        get
        {
            if (_syncObject == null)
            {
                object obj = new object();
                Interlocked.CompareExchange(ref _syncObject, obj, null);
            }
            return _syncObject;
        }
    }
}
0
Simon Mourier

Apache共通コレクションのBoundedFifoBufferを使用する別の実装を次に示します。以下のクラスは非推奨であるため、Apacheの最新のJARを使用している場合はCircularFifoQueueを使用してください

    BoundedFifoBuffer apiCallHistory = new BoundedFifoBuffer(20);

    for(int i =1 ; i < 25; i++){

        if(apiCallHistory.isFull()){
          System.out.println("removing :: "+apiCallHistory.remove());
        }
        apiCallHistory.add(i);

}
0
Raj