Coda con dimensioni limitate che contiene gli ultimi N elementi in Java


198

Una domanda molto semplice e veloce sulle librerie Java: esiste una classe già pronta che implementa una Queuecon una dimensione massima fissa - cioè consente sempre l'aggiunta di elementi, ma rimuoverà silenziosamente gli elementi head per accogliere lo spazio per gli elementi appena aggiunti.

Certo, è banale implementarlo manualmente:

import java.util.LinkedList;

public class LimitedQueue<E> extends LinkedList<E> {
    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        super.add(o);
        while (size() > limit) { super.remove(); }
        return true;
    }
}

Per quanto vedo, non esiste un'implementazione standard negli stdlibs Java, ma potrebbe essercene una in Apache Commons o qualcosa del genere?



9
@Kevin: sei così stuzzicante.
Mark Peters,

5
Personnaly Non introdurrei un'altra biblioteca se questo fosse l'unico uso di questa biblioteca ...
Nicolas Bousquet,

2
@Override public booleano add (PropagationTask t) {booleano aggiunto = super.add (t); while (aggiunta && size ()> limite) {super.remove (); } ritorno aggiunto; }
Renaud,

6
Attenzione: il codice in questione, sebbene apparentemente funzioni, potrebbe ritorcersi contro. Esistono metodi aggiuntivi che possono aggiungere più elementi alla coda (come addAll ()) che ignorano questo controllo dimensioni. Per maggiori dettagli, vedi Effective Java 2nd Edition - Item 16: Favor Composizione sull'eredità
Diego,

Risposte:


171

Le raccolte 4 di Apache commons hanno un CircularFifoQueue <> che è quello che stai cercando. Citando il javadoc:

CircularFifoQueue è una coda first-in first-out con una dimensione fissa che sostituisce il suo elemento più vecchio se pieno.

    import java.util.Queue;
    import org.apache.commons.collections4.queue.CircularFifoQueue;

    Queue<Integer> fifo = new CircularFifoQueue<Integer>(2);
    fifo.add(1);
    fifo.add(2);
    fifo.add(3);
    System.out.println(fifo);

    // Observe the result: 
    // [2, 3]

Se stai usando una versione precedente delle raccolte commons di Apache (3.x), puoi usare CircularFifoBuffer che è sostanzialmente la stessa cosa senza generici.

Aggiornamento : risposta aggiornata dopo il rilascio delle raccolte comuni versione 4 che supporta generici.


1
È un buon candidato, ma, ahimè, non usa generici :(
GreyCat

Grazie! Sembra che sia l'alternativa più praticabile per ora :)
GreyCat

3
Vedi questa altra risposta per il collegamento EvictingQueueaggiunto a Google Guava versione 15 intorno al 2013-10.
Basil Bourque,

C'è un callback chiamato quando l'elemento viene rimosso dalla coda a causa dell'aggiunta alla coda completa?
ed22,

una "coda circolare" è semplicemente un'implementazione che soddisfa la domanda. Ma la domanda non beneficia direttamente delle principali differenze di una coda circolare, vale a dire non dover liberare / riallocare ogni bucket a ogni aggiunta / eliminazione.
simpleuser,

90

Guava ora ha una EvictingQueue , una coda non bloccante che elimina automaticamente gli elementi dall'intestazione della coda quando tenta di aggiungere nuovi elementi sulla coda ed è piena.

import java.util.Queue;
import com.google.common.collect.EvictingQueue;

Queue<Integer> fifo = EvictingQueue.create(2); 
fifo.add(1); 
fifo.add(2); 
fifo.add(3); 
System.out.println(fifo); 

// Observe the result: 
// [2, 3]

Questo dovrebbe essere interessante da usare una volta uscito ufficialmente.
Asaf

Ecco la fonte: code.google.com/p/guava-libraries/source/browse/guava/src/com/… - sembra che sarebbe facile da copiare e compilare con le versioni correnti di Guava
Tom Carchrae,

1
Aggiornamento: questa classe è stata rilasciata ufficialmente con Google Guava nella versione 15 , intorno al 2013-10.
Basil Bourque,

1
@MaciejMiklas La domanda per un FIFO EvictingQueueè un FIFO. In caso di dubbi, prova questo programma: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); osserva il risultato:[2, 3]
kostmo

2
Questa è ora la risposta corretta. È un po 'poco chiaro dalla documentazione, ma EvictingQueue è un FIFO.
Michael Böckling

11

Mi piace la soluzione @FractalizeR. Ma vorrei inoltre conservare e restituire il valore da super.add (o)!

public class LimitedQueue<E> extends LinkedList<E> {

    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
           super.remove();
        }
        return added;
    }
}

1
Per quanto posso vedere, FractalizeR non ha fornito alcuna soluzione, ha solo modificato la domanda. La "soluzione" all'interno della domanda non è una soluzione, perché la domanda riguardava l'uso di una classe in una libreria standard o semi-standard, non il rotolamento della propria.
GreyCat,

3
Va sottolineato che questa soluzione non è thread-safe
Konrad Morawski,

7
@KonradMorawski l'intera classe LinkedList non è comunque thread-safe, quindi il tuo commento è inutile in questo contesto!
Renaud,

La sicurezza del thread @RenaudBlue è una preoccupazione valida (se spesso trascurata), quindi non penso che il commento sia inutile. e ricordare che LinkedListnon è sicuro per i thread non sarebbe neanche inutile. nel contesto di questa domanda, il requisito specifico di OP rende particolarmente importante che l'aggiunta di un oggetto sia eseguita come un'operazione atomica. in altre parole, il rischio di non garantire l'atomicità sarebbe maggiore che nel caso di una normale LinkedList.
Konrad Morawski,

4
Si interrompe non appena qualcuno chiama add(int,E)invece. E se addAllfunziona come previsto, dipende da dettagli di implementazione non specificati. Ecco perché dovresti preferire la delega all'eredità ...
Holger,

6

Usa la composizione non si estende (sì, intendo extends, come in un riferimento alla parola chiave extends in java e sì, questa è eredità). La composizione è superiore perché protegge completamente l'implementazione, consentendoti di modificarla senza influire sugli utenti della tua classe.

Consiglio di provare qualcosa del genere (sto digitando direttamente in questa finestra, quindi il compratore deve fare attenzione agli errori di sintassi):

public LimitedSizeQueue implements Queue
{
  private int maxSize;
  private LinkedList storageArea;

  public LimitedSizeQueue(final int maxSize)
  {
    this.maxSize = maxSize;
    storageArea = new LinkedList();
  }

  public boolean offer(ElementType element)
  {
    if (storageArea.size() < maxSize)
    {
      storageArea.addFirst(element);
    }
    else
    {
      ... remove last element;
      storageArea.addFirst(element);
    }
  }

  ... the rest of this class

Un'opzione migliore (basata sulla risposta di Asaf) potrebbe essere quella di avvolgere l' Apache Collections CircularFifoBuffer con una classe generica. Per esempio:

public LimitedSizeQueue<ElementType> implements Queue<ElementType>
{
    private int maxSize;
    private CircularFifoBuffer storageArea;

    public LimitedSizeQueue(final int maxSize)
    {
        if (maxSize > 0)
        {
            this.maxSize = maxSize;
            storateArea = new CircularFifoBuffer(maxSize);
        }
        else
        {
            throw new IllegalArgumentException("blah blah blah");
        }
    }

    ... implement the Queue interface using the CircularFifoBuffer class
}

2
+1 se spieghi perché la composizione è una scelta migliore (diversa da "preferisci la composizione rispetto all'eredità) ... e c'è un ottimo motivo
kdgregory

1
La composizione è una cattiva scelta per il mio compito qui: significa almeno il doppio del numero di oggetti => almeno due volte più spesso la raccolta dei rifiuti. Uso grandi quantità (decine di milioni) di queste code di dimensioni limitate, in questo modo: Map <Long, LimitedSizeQueue <String>>.
GreyCat,

@GreyCat - Suppongo che non hai visto come LinkedListviene implementato, quindi. L'oggetto aggiuntivo creato come un wrapper attorno all'elenco sarà piuttosto secondario, anche con "decine di milioni" di istanze.
kdgregory,

Stavo per "riduce le dimensioni dell'interfaccia", ma "protegge l'implementazione" è praticamente la stessa cosa. O risponde alle lamentele di Mark Peter sull'approccio del PO.
kdgregory,

4

L'unica cosa che so che ha uno spazio limitato è l'interfaccia BlockingQueue (che è ad esempio implementata dalla classe ArrayBlockingQueue) - ma non rimuovono il primo elemento se riempito, ma bloccano invece l'operazione put fino a quando lo spazio è libero (rimosso da altri thread ).

Per quanto ne so, la tua banale implementazione è il modo più semplice per ottenere un simile comportamento.


Ho già sfogliato le classi stdlib di Java e, purtroppo, BlockingQueuenon è una risposta. Ho pensato ad altre librerie comuni, come Apache Commons, le librerie di Eclipse, Spring, le aggiunte di Google, ecc.?
GreyCat,

3

Puoi utilizzare MinMaxPriorityQueue di Google Guava , dal javadoc:

Una coda con priorità min-max può essere configurata con una dimensione massima. In tal caso, ogni volta che la dimensione della coda supera quel valore, la coda rimuove automaticamente il suo elemento più grande in base al suo comparatore (che potrebbe essere l'elemento appena aggiunto). Ciò è diverso dalle code convenzionali limitate, che bloccano o rifiutano nuovi elementi quando sono pieni.


3
Capisci cos'è una coda prioritaria e in che modo differisce dall'esempio del PO?
kdgregory,

2
@Mark Peters - Non so proprio cosa dire. Certo, puoi fare in modo che una coda prioritaria si comporti come una coda di quindici. Potresti anche Mapcomportarti come un List. Ma entrambe le idee mostrano una completa incomprensione degli algoritmi e della progettazione del software.
kdgregory,

2
@Mark Peters - Non tutte le domande su SO sono un buon modo per fare qualcosa?
jtahlborn,

3
@jtahlborn: Chiaramente no (codice golf), ma anche se lo fossero, il bene non è un criterio in bianco e nero. Per un certo progetto, buono potrebbe significare "il più efficiente", per un altro potrebbe significare "il più facile da mantenere" e per ancora un altro, potrebbe significare "la minima quantità di codice con le librerie esistenti". Tutto ciò è irrilevante poiché non ho mai detto che questa fosse una buona risposta. Ho appena detto che può essere una soluzione senza troppi sforzi. Trasformare a MinMaxPriorityQueuein ciò che l'OP vuole è più banale che modificare un LinkedList(il codice dell'OP non si avvicina nemmeno).
Mark Peters,

3
Forse voi ragazzi state esaminando la mia scelta di parole "in pratica che sarà quasi certamente sufficiente". Non intendevo dire che questa soluzione sarebbe quasi certamente sufficiente per il problema del PO o in generale. Mi riferivo alla scelta di un longtipo di cursore discendente all'interno del mio suggerimento, dicendo che sarebbe stato sufficientemente ampio nella pratica anche se teoricamente si potevano aggiungere più di 2 ^ 64 oggetti a questa coda a quel punto la soluzione si sarebbe guastata .
Mark Peters,


-2
    public class ArrayLimitedQueue<E> extends ArrayDeque<E> {

    private int limit;

    public ArrayLimitedQueue(int limit) {
        super(limit + 1);
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
            super.remove();
        }
        return added;
    }

    @Override
    public void addLast(E e) {
        super.addLast(e);
        while (size() > limit) {
            super.removeLast();
        }
    }

    @Override
    public boolean offerLast(E e) {
        boolean added = super.offerLast(e);
        while (added && size() > limit) {
            super.pollLast();
        }
        return added;
    }
}

3
La domanda riguardava le classi nelle librerie di classi di raccolte popolari, non le proprie: la "soluzione" minimalista per l'homebrew era già stata messa in discussione.
GreyCat,

2
non importa google trova questa pagina anche su un'altra query =)
user590444

1
Questa risposta è arrivata nella coda di recensioni di bassa qualità, presumibilmente perché non fornisci alcuna spiegazione del codice. Se questo codice risponde alla domanda, considera di aggiungere un po 'di testo che spiega il codice nella tua risposta. In questo modo, hai molte più probabilità di ottenere più voti e aiutare l'interrogante a imparare qualcosa di nuovo.
lmo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.