43 votos

¿Cómo podría el código de una eficiente Búfer Circular en Java o C#

Quiero una simple clase que implementa un tamaño fijo búfer circular. Debe ser eficiente, fácil en los ojos, genéricamente escrito.

EDIT: no tiene Que ser MT-capaz, por ahora. Siempre se puede añadir un bloqueo más tarde, no será de alta concurrencia en cualquier caso.

Los métodos deben ser: .Agregar y, supongo .Lista, donde puedo ver todas las entradas. En el segundo pensamiento, la Recuperación, creo que debe ser hecho a través de un indizador. En cualquier momento me va a querer ser capaz de recuperar cualquier elemento en el búfer de índice. Pero ten en cuenta que de un momento a otro Elemento[n] puede ser diferente, ya que el buffer Circular llena y rollos de más.

Esto no es una pila, es un buffer circular. Con respecto a la "desbordamiento": yo esperaría que internamente no sería una matriz que almacena los elementos, y con el tiempo la cabeza y la cola del buffer va a girar en torno a que la matriz fija. Pero que debe ser invisible para el usuario. No debe haber ningún externamente perceptible "desbordamiento", evento o comportamiento.

Esto no es una tarea escolar - es más comúnmente va a ser utilizada por un MRU caché o un tamaño fijo de transacción o de registro de sucesos.

22voto

JeeBee Puntos 11882

Me gustaría utilizar una matriz de T, la cabeza y la cola puntero, y añadir y métodos get.

Como: (la caza de errores se deja para el usuario)

// 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();
    }
  }
}

11voto

user98166 Puntos 204

Echa un vistazo http://www.cs.utsa.edu/~wagner/CS2213/queue/queue.html para una implementación sólida de que sólo se han creado tendencia en un.

7voto

Scott S. McCoy Puntos 667

Esto es lo que haría yo (o hicieron) escribir un eficiente búfer circular en Java. Está respaldado por una matriz simple. Para mi caso concreto, yo necesitaba de alta concurrente rendimiento, así que he usado CAS para la asignación del índice. Luego he creado mecanismos para fiable de copias que incluye un CAS copia de todo el búfer. He utilizado este en un caso que se describe en mayor detalle en un artículo corto.

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

5voto

tennenrishin Puntos 453

Aquí está una lista para usar CircularArrayList aplicación de Java que voy a usar en el código de producción. Reemplazando AbstractList en Java-forma recomendada, es compatible con todas las funciones que usted esperaría de una Lista estándar de implementación en Java Collections Framework (elemento genérico tipo, sublista, iteración, etc.).

Las siguientes llamadas completa en O(1):

  • add(item) - añade al final de la lista
  • remove(0) - elimina desde el principio de la lista
  • get(i) - recupera elemento aleatorio en la lista

4voto

Esko Luontola Puntos 53877

Me gustaría utilizar ArrayBlockingQueue o uno de los otros prediseñadas Cola de implementaciones, dependiendo de cuáles son las necesidades. Muy rara vez se necesita para implementar una estructura de datos usted mismo (a menos que sea una tarea escolar).

EDIT: Ahora que usted ha añadido el requisito "para recuperar cualquier elemento en el búfer de índice", supongo que necesita para implementar su propia clase (a menos que google-colecciones o alguna otra biblioteca proporciona uno). Un buffer circular es muy fácil de implementar, como JeeBee muestra el ejemplo de la. Usted también puede buscar en ArrayBlockingQueue del código fuente - su código es bastante limpio, acaba de quitar el bloqueo e innecesarios métodos, y añadir métodos para acceder a él mediante el índice.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X