W3docs

Java ArrayDeque

Usa ArrayDeque per code e stack a doppia estremità, veloci e ridimensionabili, in Java.

ArrayDeque<E> è un array circolare che cresce su richiesta. Implementa sia Queue<E> che Deque<E>, quindi può svolgere il ruolo di coda FIFO, stack LIFO o coda a doppia estremità senza modifiche al codice. Il Javadoc di Stack raccomanda Deque al posto della classe legacy; il Javadoc di Deque aggiunge la raccomandazione pratica che ArrayDeque dovrebbe essere la tua implementazione predefinita. È quasi sempre più veloce di LinkedList, occupa meno memoria ed è la scelta preferita dalla libreria standard quando non hai bisogno dell'interfaccia List.

Perché un array circolare

Una classica "coda da array" incorre in un problema: dopo alcuni cicli di aggiunta in coda e rimozione dalla testa, l'array si riempie in fondo mentre si accumulano slot vuoti all'inizio. Una soluzione ingenua consisterebbe nello spostare tutto verso sinistra ad ogni dequeue — O(n) per rimozione, con prestazioni pessime.

Il trucco dell'array circolare risolve il problema. La classe mantiene due indici, head e tail, e li lascia avvolgere intorno:

slots:   [ . . . D E F G H A B C ]
                         ^head     ^... wraps to slot 0 ... ^tail

addFirst decrementa head, addLast incrementa tail, entrambi modulo la lunghezza dell'array. removeFirst e removeLast fanno il contrario. Ogni operazione è O(1); l'unico costo è il raddoppio dell'array di supporto quando head colliderebbe con tail, il che è ammortizzato.

Il risultato: nessuna allocazione di nodi per elemento, nessun inseguimento di puntatori, accesso cache-friendly. Questa è la ragione ingegneristica per cui ArrayDeque è solitamente la scelta più veloce per carichi di lavoro con code e stack.

Riepilogo dell'interfaccia Deque

Un Deque ha due estremità. Ogni operazione esiste in tre varianti:

OperazionePrima (testa) — lancia eccezionePrima (testa) — valore specialeUltima (coda) — lancia eccezioneUltima (coda) — valore speciale
InserimentoaddFirst(e)offerFirst(e)addLast(e)offerLast(e)
RimozioneremoveFirst()pollFirst()removeLast()pollLast()
EsamegetFirst()peekFirst()getLast()peekLast()

Inoltre, i sinonimi per Queue e stack si mappano sull'estremità della testa:

Metodo QueueEquivalente Deque
add(e) / offer(e)addLast(e) / offerLast(e)
remove() / poll()removeFirst() / pollFirst()
element() / peek()getFirst() / peekFirst()
Metodo StackEquivalente Deque
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

Quindi Deque è un'unica interfaccia che espone sia una coda che uno stack contemporaneamente, a seconda del nome con cui si chiama la testa. Abbiamo trattato queste mappature nei capitoli su Queue e Stack; Deque è il punto in cui si incontrano.

Cosa aggiunge ArrayDeque

In pratica, molto poco a livello di API — è proprio questo il punto. La classe espone il contratto Deque onestamente: le due estremità, i tre stili di errore per operazione, e clear(), iterator(), descendingIterator(), contains, size, isEmpty. L'iterazione con l'iteratore standard va dalla testa alla coda; descendingIterator è il modo economico per percorrere dalla coda alla testa senza invertire.

I costruttori rispecchiano quelli di ArrayList:

Deque<String> a = new ArrayDeque<>();          // initial capacity 16
Deque<String> b = new ArrayDeque<>(1_000);     // pre-sized
Deque<String> c = new ArrayDeque<>(other);     // from any Collection (head = first iter element)

Dimensiona preventivamente quando conosci il carico di lavoro. Il valore predefinito di 16 viene arrotondato alla potenza di due successiva, e la crescita raddoppia l'array — quindi un milione di chiamate offerLast da un deque di dimensione predefinita scatena circa 16 crescite con copia.

Le regole: niente null, non thread-safe, fail-fast

Tre regole da interiorizzare:

  1. ArrayDeque non consente elementi null. Ogni inserimento lancia NullPointerException. Così pollFirst() che restituisce null per una coda vuota rimane inequivocabile.
  2. Non è thread-safe. Due thread che mutano lo stesso ArrayDeque lo corromperanno. Per l'uso concorrente, guarda ConcurrentLinkedDeque (lock-free, illimitato) o LinkedBlockingDeque (limitato, bloccante).
  3. Iterazione fail-fast. Modificare il deque all'esterno dell'iteratore durante l'iterazione lancia ConcurrentModificationException. Usa Iterator.remove() o removeIf per mutazioni sicure.

Quando scegliere ArrayDeque rispetto alle alternative

Il flusso decisionale:

  • Hai bisogno di una Queue? → ArrayDeque. Predefinito.
  • Hai bisogno di uno stack? → ArrayDeque. Predefinito. Usa push / pop / peek.
  • Hai bisogno di un Deque? → ArrayDeque. Predefinito.
  • Hai bisogno sia della semantica Deque che di List sullo stesso oggetto? → LinkedList. Raro.
  • Hai bisogno di un ordine di priorità? → PriorityQueue. Astrazione diversa.
  • Hai bisogno di accesso concorrente? → ConcurrentLinkedDeque (illimitato) o LinkedBlockingDeque (limitato). La scelta giusta dipende da se hai bisogno di back-pressure.

Il Javadoc esprime la raccomandazione in testo chiaro: "This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue." Viene direttamente dagli autori del JDK; vale la pena prenderlo alla lettera.

Un esempio pratico: coda, stack, deque, finestra scorrevole

Il programma seguente usa un'istanza di ArrayDeque come coda FIFO, un'altra come stack LIFO e una terza come struttura di supporto per un massimo con finestra scorrevole — un problema classico per cui ArrayDeque è la risposta canonica perché si ha bisogno di mutazioni economiche su entrambe le estremità.

java— editable, runs on the server

Cosa si può ricavare da questa esecuzione:

  • Le sezioni 1 e 2 sono la stessa classe che svolge due compiti chiamando metodi diversi. Coda FIFO: offerLast / pollFirst. Stack LIFO: push / pop. Non c'è nessuna classe separata da imparare.
  • descendingIterator() è la scansione inversa economica — utile quando hai costruito un deque come stack e vuoi stamparlo dal basso verso l'alto.
  • La funzione di finestra scorrevole usa entrambe le estremità nello stesso ciclo — pollFirst per eliminare gli indici usciti dalla finestra, pollLast per mantenere uno stack monotono decrescente, peekFirst per leggere il massimo corrente. Quell'accesso a doppia estremità è il motivo per cui esiste ArrayDeque; tentare di fare questo con un ArrayList sarebbe quadratico.

Cosa c'è dopo

Hai visto come ArrayDeque implementa entrambe le estremità della storia coda/stack. L'interfaccia che ha implementato per tutto questo tempo merita un capitolo proprio — Deque è il contratto che unisce tutto ciò che abbiamo trattato sulle collezioni simili a code. Lo affronteremo nella prossima sessione.

Esercitazione

Pratica
Hai bisogno di uno stack LIFO veloce di token `String` e sai che itererai i contenuti dal basso verso l'alto per il debug. Quale dichiarazione corrisponde alla raccomandazione moderna?
Hai bisogno di uno stack LIFO veloce di token `String` e sai che itererai i contenuti dal basso verso l'alto per il debug. Quale dichiarazione corrisponde alla raccomandazione moderna?
Was this page helpful?