1. Storia diLinkedList
Java ha un'altra classe di raccolta Java ereditata dal linguaggio C++. Questa è la LinkedList
classe, che implementa una "lista collegata".
Esternamente, a LinkedList
sembra essere uguale a un ArrayList
. La LinkedList
classe ha tutti gli stessi metodi della ArrayList
classe. In linea di principio, puoi sempre usare a LinkedList
invece di an ArrayList
e tutto funzionerà.
Allora perché abbiamo bisogno di un'altra classe di elenchi?
La risposta ha tutto a che fare con la struttura interna della LinkedList
classe. Invece di un array, utilizza un elenco doppiamente collegato . Spiegheremo di cosa si tratta un po' più tardi.
La LinkedList
diversa struttura interna della classe rende più veloce l'inserimento di elementi al centro dell'elenco.
Su Internet, puoi spesso trovare confronti tra ArrayList
e LinkedList
classi:
Operazione | Metodo | Lista di array | Lista collegata |
---|---|---|---|
Aggiungi un elemento |
|
Veloce | Molto veloce |
Inserisci un elemento |
|
Lento | Molto veloce |
Ottieni un elemento |
|
Molto veloce | Lento |
Imposta un elemento |
|
Molto veloce | Lento |
Rimuovi un elemento |
|
Lento | Molto veloce |
Tutto sembra abbastanza chiaro: se hai bisogno di inserire spesso elementi nella lista, usa LinkedList
; se raramente, usa ArrayList. Ma la realtà è un po' diversa.
2. Nessuno usaLinkedList
Nessuno usa LinkedList
.
Anche l'autore della LinkedList
classe ha recentemente twittato: "Qualcuno lo usa davvero LinkedList
? L'ho scritto e non lo uso mai".
Allora qual è il problema?
In primo luogo, la ArrayList
classe ha iniziato a essere in grado di inserire molto rapidamente gli elementi al centro dell'elenco. Quando si inserisce un elemento al centro dell'elenco, è necessario spostare tutti gli elementi dopo il punto di inserimento di 1 verso la fine dell'elenco. Questo richiedeva tempo.
Ma ora tutto è cambiato. Tutti gli elementi di un array sono vicini tra loro nello stesso blocco di memoria, quindi l'operazione di spostamento degli elementi viene eseguita da un comando di basso livello molto veloce: .System.arraycopy()
Inoltre, i processori odierni dispongono di una cache di grandi dimensioni che di solito può contenere l'intero array, il che consente di spostare gli elementi dell'array all'interno della cache anziché in memoria. Un milione di elementi possono essere facilmente spostati in un solo millisecondo.
In secondo luogo, la LinkedList
classe può inserire rapidamente elementi se li inserisci utilizzando un iteratore. Se usi un iteratore per passare attraverso a LinkedList
e inserire costantemente nuovi elementi (o rimuovere quelli esistenti), l'operazione è super veloce.
Se aggiungi semplicemente elementi a un LinkedList
oggetto all'interno di un ciclo, ogni operazione di inserimento veloce è accompagnata da una lenta operazione di "ottenere elemento".
La realtà è molto più vicina a questa:
Operazione | Metodo | Lista di array | Lista collegata |
---|---|---|---|
Aggiungi un elemento |
|
Veloce | Molto veloce |
Inserisci un elemento |
|
Lento | Molto lento |
Ottieni un elemento |
|
Molto veloce | Molto lento |
Imposta un elemento |
|
Molto veloce | Molto lento |
Rimuovi un elemento |
|
Lento | Molto lento |
Inserisci usando un iteratore |
|
Lento | Molto veloce |
Rimuovi usando un iteratore |
|
Lento | Molto veloce |
Perché ottenere un elemento da LinkedList
un'operazione così lenta?
Puoi rispondere a questa domanda dopo aver familiarizzato un po' con come LinkedList
è strutturato.
3. Com'è LinkedList
strutturato
LinkedList
ha una struttura interna diversa da ArrayList
. Non ha un array interno per la memorizzazione di elementi. Invece, utilizza una struttura di dati chiamata lista doppiamente inchiostrata .
Ogni elemento di una lista doppiamente collegata memorizza un riferimento all'elemento precedente e all'elemento successivo. Questo è un po' come una fila in un negozio, dove ogni persona ricorda la persona in piedi di fronte a loro così come la persona in piedi dietro di loro.
Ecco come appare un elenco di questo tipo in memoria:
La testa e la coda (celle con sfondo grigio) sono le variabili first
e last
, che memorizzano i riferimenti agli Node
oggetti.
Nel mezzo, hai una catena di Node
oggetti (oggetti, non variabili). Ciascuno di essi è composto da tre campi:
prev
— memorizza un riferimento (collegamento) all'oggetto precedenteNode
(celle con sfondo giallo).value
— memorizza il valore di questo elemento dell'elenco (celle con sfondo verde).next
— memorizza un riferimento (collegamento) all'oggetto successivoNode
(celle con sfondo blu)
Il secondo oggetto (indirizzo F24) è il successivo ( next
) per il primo oggetto e il precedente ( prev
) per il terzo oggetto. Il campo giallo del terzo oggetto contiene l'indirizzo F24 e il campo blu del primo oggetto contiene l'indirizzo F24.
Le frecce del primo e del terzo oggetto puntano allo stesso secondo oggetto. Quindi sarebbe più corretto disegnare le frecce in questo modo.
4. Inserire un elemento in un elenco collegato
Per aggiungere qualcuno alla fila in questo modo, devi solo ottenere il permesso di due persone in piedi una accanto all'altra. La prima persona ricorda il nuovo arrivato come "la persona dietro di me", e la seconda persona lo ricorda come "la persona davanti a me".
Tutto quello che devi fare è cambiare i riferimenti dei due oggetti vicini:
Abbiamo aggiunto un nuovo elemento alla nostra lista modificando i riferimenti del secondo e del terzo oggetto. Il nuovo oggetto è il successivo per il vecchio secondo oggetto e il precedente per il vecchio terzo oggetto. E, naturalmente, il nuovo oggetto stesso deve memorizzare i riferimenti corretti: il suo oggetto precedente è il vecchio secondo oggetto e il suo oggetto successivo è il vecchio terzo oggetto.
Rimuovere un elemento è ancora più semplice: se vogliamo rimuovere, diciamo, il centesimo oggetto dall'elenco, dobbiamo solo cambiare il campo next
per il 99esimo oggetto in modo che punti al 101esimo oggetto e cambiare il prev
campo per il 101esimo oggetto in modo che punti al 99esimo. Questo è tutto.
Ma ottenere il centesimo oggetto non è così facile.
5. Rimuovere un elemento da un elenco
Per ottenere il centesimo elemento di un elenco collegato, devi:
Ottieni il primo oggetto: lo fai usando la first
variabile nell'oggetto LinkedList
. Il next
campo del primo oggetto memorizza un riferimento al secondo oggetto. Ecco come otteniamo il secondo oggetto. Il secondo oggetto ha un riferimento al terzo e così via.
Se dobbiamo ottenere un riferimento al centesimo oggetto, dobbiamo spostarci in sequenza attraverso tutti gli oggetti dal primo al centesimo. E se abbiamo bisogno del milionesimo elemento in un elenco, dobbiamo iterare oltre un milione di oggetti uno dopo l'altro!
E se questi oggetti sono stati aggiunti all'elenco in momenti diversi, si troveranno in parti diverse della memoria ed è improbabile che finiscano contemporaneamente nella cache del processore. Ciò significa che l'iterazione sugli elementi di un elenco collegato non è solo lenta, ma molto lenta.
Questo è ciò con cui abbiamo a che fare.
Allora perché ti stiamo insegnando come LinkedList
funziona questo rallentamento?
Bene, durante i colloqui di lavoro ti verrà chiesto in cosa LinkedList
differisce daArrayList
. Decisamente.
GO TO FULL VERSION