Как бы вы кодировали эффективный кольцевой буфер в Java или C #? - PullRequest
43 голосов
/ 26 февраля 2009

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

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

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

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

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

Ответы [ 13 ]

22 голосов
/ 26 февраля 2009

Я бы использовал массив T, указатель головы и хвоста, а также методы add и get.

Нравится: (Охота за ошибками предоставлена ​​пользователю)

// 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();
    }
  }
}
6 голосов
/ 25 мая 2010

Вот как я бы (или сделал) написал эффективный циклический буфер в 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;
    }
}
5 голосов
/ 04 июля 2011

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

Следующие вызовы завершены в O (1):

  • добавить (элемент) - добавляет в конец списка
  • удалить (0) - удаляет из начала списка
  • get (i) - возвращает случайный элемент в списке
4 голосов
/ 26 февраля 2009

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

РЕДАКТИРОВАТЬ: Теперь, когда вы добавили требование «извлекать любой элемент из буфера по индексу», я полагаю, что вам нужно реализовать свой собственный класс (если google-collection или какая-либо другая библиотека не предоставляет один). Циклический буфер довольно прост в реализации, как показывает пример JeeBee. Вы также можете посмотреть на исходный код ArrayBlockingQueue - его код довольно чистый, просто удалите блокирующие и ненужные методы и добавьте методы для доступа к нему по индексу.

3 голосов
/ 08 декабря 2011

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

2 голосов
/ 26 февраля 2009

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

Power Collections Deque <T> реализовано кольцевым буфером.

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

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

1 голос
/ 06 марта 2017

Я хочу ответить на этот вопрос с точки зрения 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());

    }
}
1 голос
/ 31 июля 2015

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

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

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

1 голос
/ 10 мая 2010

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

0 голосов
/ 04 июня 2014

Вот еще одна реализация, которая использует BoundedFifoBuffer общей коллекции Apache. используйте 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);

}
...