44 votes

Comment coderiez-vous un tampon circulaire efficace en Java ou en C #

Je veux une simple classe qui implémente une taille fixe de mémoire tampon circulaire. Elle doit être efficace, facile sur les yeux, de manière générique tapé.

EDIT: Il n'a pas besoin d'être MT-capable, pour l'instant. Je peux toujours ajouter un verrou plus tard, il ne sera pas en haut de la simultanéité dans tous les cas.

Les méthodes doivent être: .Ajouter et, je suppose .Liste, où j'ai récupérer toutes les entrées. Sur la deuxième pensée, de la Récupération, je pense, devrait être fait par un indexeur. À tout moment je veux être capable de récupérer n'importe quel élément dans la mémoire tampon par l'index. Mais gardez à l'esprit que d'un moment à l'Élément suivant[n] peut être différent, comme la Circulaire de la mémoire tampon se remplit et se retourne.

Ce n'est pas une pile, c'est une mémoire tampon circulaire. Concernant le "dépassement": je pense à l'interne, il y aurait une table contenant les éléments, et au fil du temps la tête et la queue de la mémoire tampon va tourner autour de ce tableau fixe. Mais qui doit être invisible pour l'utilisateur. Il ne devrait pas être à l'extérieur-détectable "dépassement" de l'événement ou d'un comportement.

Ce n'est pas un devoir scolaire, il est le plus couramment utilisé pour un MRU cache ou d'une taille fixe de transaction ou le journal des événements.

22voto

JeeBee Points 11882

Je voudrais utiliser un tableau de T, un pointeur de tête et la queue, et ajouter et obtenir des méthodes.

Comme: (la recherche de bogues est laissée à l'utilisateur)

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

Consultez http://www.cs.utsa.edu/~wagner/CS2213/queue/queue.html pour une implémentation solide qui doit simplement être généralisée.

8voto

Scott S. McCoy Points 667

C'est comment je pourrais (ou a) écrire un efficace tampon circulaire en Java. Il est appuyé par un simple tableau. Pour mon cas d'utilisation particulier, j'avais besoin de haut débit concurrent, j'ai donc utilisé la CAS pour l'allocation de l'indice. J'ai ensuite créé des mécanismes pour la fiabilité des copies, y compris un CAS exemplaire de l'ensemble de la mémoire tampon. J'ai utilisé ce dans un cas qui est décrit plus en détail dans un court article.

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 Points 453

Ici, c'est un prêt-à-utiliser CircularArrayList mise en œuvre de Java que j'utilise dans le code de production. En substituant AbstractList dans la Java de la façon recommandée, il prend en charge toutes les fonctionnalités que vous attendez d'une Liste standard de mise en œuvre en Java Collections Cadre générique (type d'élément, les sous-liste, l'itération, etc.).

Les appels suivants complet en O(1):

  • add(item) - ajoute à la fin de la liste
  • supprimer(0) - supprime à partir du début de la liste
  • get(i)): récupère l'élément aléatoire dans la liste

4voto

Esko Luontola Points 53877

Je voudrais utiliser ArrayBlockingQueue ou l'une de l'autre, prêts à l'emploi de la File d'attente des mises en œuvre, selon les besoins. Très rarement, il est nécessaire de mettre en œuvre une telle structure de données vous-même (sauf si c'est une école d'affectation).

EDIT: Maintenant que vous avez ajouté l'exigence "de récupérer n'importe quel élément dans la mémoire tampon par index", je suppose que vous avez besoin pour mettre en œuvre votre propre classe (sauf google-collections ou d'une autre bibliothèque fournit un). Un tampon circulaire est assez facile à mettre en œuvre, comme JeeBee le montre l'exemple de. Vous pouvez également regarder ArrayBlockingQueue du code source, le code est très propre, il suffit de retirer le verrouillage et inutiles méthodes, et ajouter des méthodes pour y accéder par l'index.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X