it-swarm.com.de

Wie würden Sie einen effizienten Circular Buffer in Java oder C # programmieren?

Ich möchte eine einfache Klasse, die einen kreisförmigen Puffer mit fester Größe implementiert. Es sollte effizient, augenschonend und generisch typisiert sein. 

EDIT: Es muss vorerst nicht MT-fähig sein. Ich kann später immer eine Sperre hinzufügen, es ist jedoch in keinem Fall eine hohe Parallelität.

Methoden sollten sein: .Add und ich denke, .List, wo ich alle Einträge abrufen. Auf den zweiten Gedanken, Retrieval, denke ich, sollte über einen Indexer erfolgen. Zu jedem Zeitpunkt möchte ich jedes Element im Puffer nach Index abrufen können. Denken Sie jedoch daran, dass das Element [n] von einem Moment zum nächsten unterschiedlich sein kann, da sich der Circular-Puffer füllt und überrollt.

Dies ist kein Stapel, sondern ein Ringspeicher. In Bezug auf "Überlauf": Ich würde erwarten, dass es intern ein Array gibt, das die Elemente enthält, und im Laufe der Zeit werden sich der Kopf und das Ende des Puffers um dieses feste Array drehen. Das sollte aber vom Benutzer nicht sichtbar sein. Es sollte kein extern erkennbares "Überlauf" -Ereignis oder -Verhalten geben.

Dies ist keine Schulaufgabe, sondern wird meistens für einen MRU-Cache oder für ein Transaktions- oder Ereignisprotokoll fester Größe verwendet.

43
Cheeso

Ich würde ein Array von T, einen Kopf- und einen Endzeiger verwenden und Methoden hinzufügen und abrufen.

Wie: (Fehlersuche ist dem Benutzer überlassen)

// 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

So würde ich (oder habe) einen effizienten Ringpuffer in Java schreiben. Es wird von einem einfachen Array unterstützt. Für meinen speziellen Anwendungsfall benötigte ich einen hohen gleichzeitigen Durchsatz. Daher verwendete ich CAS für die Indexzuordnung. Anschließend erstellte ich Mechanismen für zuverlässige Kopien, einschließlich einer CAS-Kopie des gesamten Puffers. Ich habe dies in einem Fall verwendet, der in kurzer Artikel ausführlicher beschrieben wird.

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;
    }
}
6
Scott S. McCoy

Hier ist eine gebrauchsfertige CircularArrayList-Implementierung für Java , die ich im Produktionscode verwende. Durch das Überschreiben von AbstractList auf die von Java empfohlene Weise unterstützt es alle Funktionen, die Sie von einer standardmäßigen List-Implementierung im Java Collections Framework erwarten würden (generischer Elementtyp, Unterliste, Iteration usw.).

Die folgenden Aufrufe werden in O (1) ausgeführt:

  • add (item) - fügt am Ende der Liste hinzu
  • remove (0) - Entfernt den Anfang der Liste
  • get (i) - ruft ein zufälliges Element in der Liste ab
4
Museful

Ich würde ArrayBlockingQueue oder eine der anderen vorgefertigten Warteschlangenimplementierungen verwenden, abhängig von den Bedürfnissen. Sehr selten besteht die Notwendigkeit, eine solche Datenstruktur selbst zu implementieren (es sei denn, es handelt sich um eine Schulaufgabe).

BEARBEITEN: Nachdem Sie nun die Anforderung hinzugefügt haben, "ein beliebiges Element aus dem Puffer nach Index abzurufen", müssen Sie vermutlich Ihre eigene Klasse implementieren (es sei denn, google-collection oder eine andere Bibliothek stellt eine zur Verfügung). Ein Ringspeicher ist recht einfach zu implementieren, wie das Beispiel von JeeBee zeigt. Sie können sich auch den Quellcode von ArrayBlockingQueue ansehen - der Code ist ziemlich sauber. Entfernen Sie einfach die sperrenden und nicht mehr benötigten Methoden und fügen Sie Methoden für den Zugriff per Index hinzu.

4
Esko Luontola

Verwenden Sie Javas ArrayDeque

3

Verwenden Sie einfach die Implementierung einer anderen Person:

Die Power CollectionsDeque<T> wird durch einen Ringspeicher implementiert. 

Die Power Collections-Bibliothek ist lückenhaft, der Deque ist jedoch durchaus akzeptabel, um den Ringspeicher zu erweitern.

Da Sie angeben, dass Sie keine Erweiterung wünschen und stattdessen eine Überschreibung wünschen, können Sie den zu überschreibenden Code relativ einfach ändern. Dies würde einfach bedeuten, die Prüfung auf logisch benachbarte Zeiger zu entfernen und sowieso nur zu schreiben. Zur gleichen Zeit konnte der private Puffer nur lesbar gemacht werden.

2
ShuggyCoUk

Ich möchte diese Frage in der Java-Perspektive beantworten.

Um einen zirkularen Puffer mit Java zu implementieren, benötigen Sie wahrscheinlich drei Dinge: eine zirkulare Pufferklasse, eine generische Klasse und einige Operationen. (Um zu erfahren, welche Operationen Sie benötigen, und den inneren Mechanismus in diesen Operationen, müssen Sie möglicherweise Wiki für Ringpuffer zuerst).

Zweitens sollte das Urteil über Puffer voll oder leer sehr sorgfältig behandelt werden. Hier gebe ich zwei instinktive Lösungen für das volle/leere Urteil. In Lösung 1 müssen Sie zwei Ganzzahlvarianten erstellen, um sowohl die aktuelle Größe Ihres Puffers als auch die maximale Größe Ihres Puffers zu speichern. Wenn die aktuelle Größe der maximalen Größe entspricht, ist der Puffer offensichtlich voll. 

Bei einer anderen Lösung setzen wir den letzten Speicherplatz im Leerlauf (für Ringpuffer mit Größe sieben den Speicher auf sieben im Leerlauf). Demnach können wir feststellen, dass der Puffer voll ist, wenn der Ausdruck (rp+1)%MAXSIZE == fp; erfüllt ist.

Zur weiteren Verdeutlichung gibt es hier Implementierungen mit der Java-Sprache.

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

In Guava 15 haben wir EvictingQueue eingeführt, eine nicht blockierende, begrenzte Warteschlange, die automatisch Elemente aus dem Kopf der Warteschlange entfernt (entfernt), wenn versucht wird, Elemente zu einer vollständigen Warteschlange hinzuzufügen. Dies unterscheidet sich von herkömmlichen beschränkten Warteschlangen, die neue Elemente entweder blockieren oder ablehnen, wenn sie voll sind.

Es klingt so, dass dies Ihren Bedürfnissen entsprechen sollte, und hat eine viel einfachere Benutzeroberfläche als die direkte Verwendung einer ArrayDeque (sie verwendet jedoch eine unter der Haube!).

Weitere Informationen finden Sie hier .

1

System.Collections.Generic.Queue - ist ein einfacher kreisförmiger Puffer (T [] mit Kopf und Schwanz wie in sample von JeeBee ). 

1
Zakus
0
Ray Tayek

Hier ist eine Implementierung, die ich für meinen eigenen Gebrauch codiert habe, die aber nützlich sein könnte.

Der Puffer enthält eine maximale Anzahl festgelegter Elemente. Das Set ist kreisförmig, alte Elemente werden automatisch entfernt. Der Anrufer kann Elemente über einen absoluten inkrementellen Index (einen Long-Index) abschließen, jedoch sind Elemente möglicherweise zwischen zeitlich zu weit entfernten Anrufen verloren gegangen. Diese Klasse ist vollständig threadsicher.

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

Hier ist eine weitere Implementierung, die den BoundedFifoBuffer der allgemeinen Sammlung von Apache verwendet. Bitte verwenden Sie CircularFifoQueue, wenn Sie das neueste JAR von Apache verwenden, da die Klasse unten nicht mehr empfohlen wird 

    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