Как бы вы кодировали эффективный кольцевой буфер в Java или С#?

Я хочу простой класс, который реализует кольцевой буфер фиксированного размера. Это должно быть эффективно, легко для глаз, типично напечатано.

На данный момент он не должен быть МТ-способным. Я всегда могу добавить блокировку позже, это не будет высокий параллелизм в любом случае.

Методы должны быть: .Add() и, я думаю, .List(), где я получаю все записи. Во-вторых, поиск, я думаю, должен быть сделан с помощью индексатора. В любой момент я захочу получить любой элемент в буфере по индексу. Но имейте в виду, что от одного момента к следующему элемент [n] может отличаться, так как круговой буфер заполняется и переворачивается. Это не стек, это круговой буфер.

Что касается "переполнения": я ожидал бы, что внутри будет массив, содержащий элементы, и со временем голова и хвост буфера будут вращаться вокруг этого фиксированного массива. Но это должно быть невидимым для пользователя. Не должно быть обнаруживаемого извне события или поведения "переполнения".

Это не школьное задание - чаще всего оно будет использоваться для кэша MRU или журнала транзакций или событий фиксированного размера.

Ответ 1

Я бы использовал массив 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 shift on one
        b.add("Foo");
        b.get();
    }
  }
}

Ответ 2

Вот как я (или сделал) написал эффективный круговой буфер в 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 size is <em>unstable</em>.
 * It 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 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 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 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;
    }
}

Ответ 3

Я бы использовал ArrayBlockingQueue или одну из других готовых реализаций Queue, в зависимости от потребностей. Очень редко необходимо реализовать такую ​​структуру данных самостоятельно (если это не школьное задание).

EDIT: теперь, когда вы добавили требование "получить любой элемент в буфере по индексу", я полагаю, что вам нужно реализовать свой собственный класс (если google-collections или какая-то другая библиотека). Круговой буфер довольно просто реализовать, как показывает пример JeeBee. Вы также можете посмотреть исходный код ArrayBlockingQueue - его код довольно чистый, просто удалите блокирующие и ненужные методы и добавьте методы для доступа к нему по индексу.

Ответ 4

Вот готовая к использованию программа CircularArrayList для Java, которую я использую в производственном коде. Переопределяя AbstractList в рекомендованном Java образом, он поддерживает все функциональные возможности, которые вы ожидаете от стандартной реализации List в структуре коллекций Java (тип универсального элемента, subList, итерация и т.д.).

В O (1) завершаются следующие вызовы:

  • add (item) - добавляет в конце списка
  • remove (0) - удаляет с начала списка
  • get (i) - извлекает случайный элемент в списке

Ответ 5

Использовать Java ArrayDeque

Ответ 6

Просто используйте чужую реализацию:

Коллекции мощностей Deque <T> реализуется круговым буфером.

Библиотека коллекций мощностей неоднородна, но Deque - вполне приемлемый расширяющийся круговой буфер.

Поскольку вы указываете, что не хотите расширения и вместо этого хотите перезаписать, вы можете довольно легко изменить код для перезаписи. Это просто связано с удалением проверки того, что указатели логически смежны и просто записываются в любом случае. В то же время частный буфер может быть сделан только для чтения.

Ответ 7

System.Collections.Generic.Queue - простой круговой буфер внутри (T [] с головой и хвостом, как в примере от JeeBee).

Ответ 8

В Guava 15 мы представили EvictingQueue, которая является неблокирующей, ограниченной очередью, которая автоматически вытесняет (удаляет) элементы от главы очереди при попытке добавить элементы в полную очередь. Это отличается от обычных ограниченных очередей, которые либо блокируют, либо отклоняют новые элементы при заполнении.

Похоже, что это должно соответствовать вашим потребностям и имеет гораздо более простой интерфейс, чем использование ArrayDeque напрямую (он использует один под капотом, хотя!).

Более подробную информацию можно найти здесь.

Ответ 9

Я хочу ответить на этот вопрос в java-перспективе.

Чтобы реализовать циклический буфер с java, вам, вероятно, понадобятся три вещи, включая: круглый буферный класс, общие и несколько операций над ним (чтобы узнать, какие операции вам нужны, и внутренний механизм в этой операции, вам может потребоваться сначала прочитайте wiki для кругового буфера.

Во-вторых, суждение о полном или пустом буфере должно рассматриваться очень осторожно. Здесь я даю два инстинктивных решения для полного/пустого суждения. В одном решении вам нужно создать два целочисленных варианта для хранения как текущего размера вашего буфера, так и максимального размера вашего буфера. Очевидно, что если текущий размер равен максимальному размеру, буфер заполняется.

В другом решении мы устанавливаем последнее место хранения в режиме ожидания (для кругового буфера с размером семь, мы устанавливаем хранилище в семь в режиме ожидания). В соответствии с этим мы можем определить, что буфер заполнен, когда выполняется выражение (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());

    }
}

Ответ 11

Вот реализация, которую я закодировал для собственного использования, но это может быть полезно.

В буфере содержится максимальный фиксированный набор элементов. Набор является круглым, старые элементы автоматически удаляются. Вызывающий может получить хвост элементов по абсолютному инкрементному индексу (длинный), но предметы могут быть потеряны между слишком удаленными во времени вызовами. Этот класс полностью потокобезопасен.

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;
        }
    }
}

Ответ 12

Вот еще одна реализация, которая использует общую коллекцию Apache BoundedFifoBuffer. используйте CircularFifoQueue, если вы используете последний JAR из Apache, поскольку ниже класс устарел

    BoundedFifoBuffer apiCallHistory = new BoundedFifoBuffer(20);

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

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

}

Ответ 13

// The following is in C#

public class fqueue
{

  // The following code implements a circular queue of objects

  //private data:

    private bool empty;
    private bool full;

    private int begin, end;

    private object[] x;

  //public data:

    public fqueue()
    {
        empty = !(full = false);
        begin = end = 0xA2;

        x = new object[256];
        return;
    }

    public fqueue(int size)
    {
        if (1 > size) throw new Exception("fqueue: Size cannot be zero or negative");

        empty = !(full = false);
        begin = end = 0xA2;

        x = new object[size];
        return;
    }

    public object write
    {
        set
        {
            if(full) throw new Exception("Write error: Queue is full");

            end = empty ? end : (end + 1) % x.Length;

            full = ((end + 1) % x.Length) == begin;
            empty = false;

            x[end] = value;
        }
    }

    public object read
    {
        get
        {
            if(empty) throw new Exception("Read error: Queue is empty");
            full = false;

            object ret = x[begin];

            begin = (empty=end==begin) ?
                begin :
                (begin + 1) % x.Length;

            return ret;
        }
    }

    public int maxSize
    {
        get
        {
            return x.Length;
        }
    }

    public int queueSize
    {
        get
        {
            return end - begin + (empty ? 0 : 1 + ((end < begin) ? x.Length : 0));
        }
    }

    public bool isEmpty
    {
        get
        {
            return empty;
        }
    }

    public bool isFull
    {
        get
        {
            return full;
        }
    }

    public int start
    {
        get
        {
            return begin;
        }
    }        

    public int finish
    {
        get
        {
            return end;
        }
    }
}