1. Historia omLinkedList
Java har en annan samlingsklass Java ärvt från C++-språket. Det här är LinkedList
klassen som implementerar en "länkad lista".
Till det yttre LinkedList
verkar a vara detsamma som ett ArrayList
. Klassen LinkedList
har alla samma metoder som ArrayList
klassen. I princip kan du alltid använda en LinkedList
istället för en ArrayList
och allt kommer att fungera.
Så varför behöver vi en annan listklass?
Svaret har allt att göra med klassens interna struktur LinkedList
. Istället för en array använder den en dubbellänkad lista . Vi kommer att förklara vad det är lite senare.
Klassens LinkedList
olika interna struktur gör den snabbast på att infoga element i mitten av listan.
På Internet kan du ofta hitta jämförelser av klasserna ArrayList
och LinkedList
:
Drift | Metod | ArrayList | Länkad lista |
---|---|---|---|
Lägg till ett element |
|
Snabb | Väldigt snabbt |
Infoga ett element |
|
Långsam | Väldigt snabbt |
Skaffa ett element |
|
Väldigt snabbt | Långsam |
Ställ in ett element |
|
Väldigt snabbt | Långsam |
Ta bort ett element |
|
Långsam | Väldigt snabbt |
Allt verkar tydligt nog: om du behöver infoga element i listan ofta, använd LinkedList
; om sällan, använd ArrayList. Men verkligheten är lite annorlunda.
2. Ingen använderLinkedList
Ingen använder LinkedList
.
Till och med klassens författare LinkedList
twittrade nyligen: "Använder någon faktiskt LinkedList
? Jag skrev det, och jag använder det aldrig."
Så vad är affären?
Först började klassen ArrayList
mycket snabbt kunna infoga element i mitten av listan. När du infogar ett element i mitten av listan måste du flytta alla element efter insättningspunkten med 1 mot slutet av listan. Detta brukade ta tid.
Men nu har allt förändrats. Alla element i en array är nära varandra i samma minnesblock, så operationen för att flytta elementen utförs av ett mycket snabbt lågnivåkommando: .System.arraycopy()
Dessutom har dagens processorer en stor cache som vanligtvis rymmer hela arrayen, vilket gör att arrayelementen kan flyttas inuti cachen snarare än i minnet. En miljon element skiftas lätt på en enda millisekund.
För det andra kan klassen LinkedList
infoga element snabbt om du infogar dem med en iterator. Om du använder en iterator för att gå igenom en LinkedList
och hela tiden infoga nya element (eller ta bort befintliga) går operationen supersnabb.
Om du helt enkelt lägger till element till ett LinkedList
objekt inuti en loop, så åtföljs varje snabb infogningsoperation av en långsam "get element"-operation.
Verkligheten är mycket närmare detta:
Drift | Metod | ArrayList | Länkad lista |
---|---|---|---|
Lägg till ett element |
|
Snabb | Väldigt snabbt |
Infoga ett element |
|
Långsam | Väldigt långsam |
Skaffa ett element |
|
Väldigt snabbt | Väldigt långsam |
Ställ in ett element |
|
Väldigt snabbt | Väldigt långsam |
Ta bort ett element |
|
Långsam | Väldigt långsam |
Infoga med en iterator |
|
Långsam | Väldigt snabbt |
Ta bort med en iterator |
|
Långsam | Väldigt snabbt |
LinkedList
Varför är det så långsamt att få ett element från en operation?
Du kan svara på den frågan efter att ha bekantat dig lite med hur det LinkedList
är uppbyggt.
3. Hur LinkedList
är uppbyggt
LinkedList
har en annan intern struktur än ArrayList
. Den har ingen intern array för att lagra element. Istället använder den en datastruktur som kallas en dubbelfärgad lista .
Varje element i en dubbellänkad lista lagrar en referens till föregående element och nästa element. Detta är ungefär som en rad i en butik, där varje person kommer ihåg personen som står framför dem såväl som personen som står bakom dem.
Så här ser en sådan lista ut i minnet:
Huvudet och svansen (celler med grå bakgrund) är variablerna first
och last
som lagrar referenser till Node
objekt.
I mitten har du en kedja av Node
objekt (objekt, inte variabler). Var och en av dem består av tre fält:
prev
— lagrar en referens (länk) till föregåendeNode
objekt (celler med gul bakgrund).value
— lagrar värdet för detta element i listan (celler med grön bakgrund).next
— lagrar en referens (länk) till nästaNode
objekt (celler med blå bakgrund)
Det andra objektet (adress F24) är nästa ( next
) för det första objektet och det föregående ( prev
) för det tredje objektet. Det gula fältet för det tredje objektet innehåller adressen F24 och det blå fältet för det första objektet innehåller adressen F24.
Pilarna från det första och tredje objektet pekar på samma andra objekt. Så det vore mer korrekt att rita pilarna så här.
4. Infoga ett element i en länkad lista
För att lägga till någon i rad som denna behöver du bara få tillstånd från två personer som står bredvid varandra. Den första personen minns nykomlingen som "personen bakom mig", och den andra personen minns dem som "personen framför mig".
Allt du behöver göra är att ändra referenserna för de två angränsande objekten:
Vi lade till ett nytt objekt till vår lista genom att ändra referenserna för det andra och tredje objektet. Det nya objektet är nästa för det gamla andra objektet och det föregående för det gamla tredje objektet. Och, naturligtvis, måste det nya objektet självt lagra de korrekta referenserna: dess tidigare objekt är det gamla andra objektet, och dess nästa objekt är det gamla tredje objektet.
Att ta bort ett element är ännu enklare: Om vi vill ta bort, låt oss säga, det 100:e objektet från listan, behöver vi bara ändra fältet next
för det 99:e objektet så att det pekar på det 101:a objektet och ändra fältet prev
för det 101:a objektet objekt så att det pekar mot 99:an. Det är allt.
Men att få det 100:e objektet är inte så lätt.
5. Ta bort ett element från en lista
För att få det 100:e elementet i en länkad lista måste du:
Hämta det 1:a objektet: Du gör detta med first
variabeln i LinkedList
objektet. Fältet next
för det 1:a objektet lagrar en referens till det 2:a objektet. Det är så vi får det andra objektet. Det andra objektet har en referens till det tredje, och så vidare.
Om vi behöver få en referens till det 100:e objektet måste vi sekventiellt flytta igenom alla objekt från det 1:a till det 100:e. Och om vi behöver det miljonte elementet i en lista måste vi iterera över en miljon objekt efter varandra!
Och om dessa objekt lades till i listan vid olika tidpunkter, kommer de att finnas i olika delar av minnet och kommer sannolikt inte att hamna i processorns cache samtidigt. Det betyder att det att iterera över elementen i en länkad lista inte bara är långsamt, utan väldigt långsamt.
Det är vad vi har att göra med.
Så varför lär vi dig hur det här långsamt LinkedList
fungerar?
Tja, under anställningsintervjuer kommer du att få frågan hur det LinkedList
skiljer sig frånArrayList
. Definitivt.
GO TO FULL VERSION