前言
LinkedList是一個以雙向鏈表實現的List,它除了作為List使用,還可以作為隊列或者堆棧使用。
LinkedList介紹
LinkedList繼承關系
LinkedList簡介
LinkedList
是一個繼承於AbstractSequentialList
的雙向鏈表。它也可以被當做堆棧、隊列或雙端隊列進行使用。LinkedList
實現List
接口,能讓它進行隊列操作。LinkedList
實現Deque
接口,即能將LinkedList
當做雙端隊列使用。LinkedList
實現Cloneable
,即覆蓋了函數clone()
,能被克隆。LinkedList
實現了java.io.Serializable
接口,這意味着LinkedList
支持序列化,能通過序列化去傳輸。LinkedList
中的操作不是線程安全的。
LinkedList源碼分析
AbstractSequentialList介紹
我們在看LinkedList
之前先簡單介紹下其父類AbstractSequentialList
。
AbstractSequentialList
繼承自AbstractList
,是List
接口的簡化版實現。
AbstractSequentialList
只支持按次序訪問(鏈表在內存中不是按順序存放的,而是通過指針連在一起,為了訪問某一元素,必須從鏈頭開始順着指針才能找到某一個元素。),不像AbstractList
可以隨機訪問。
想要實現一個支持次序訪問的List的話,只需要繼承這個類,並實現的指定的方法listIterator(int index)
和size()
。實現ListIterator
的hasNext()
、next()
、hasPrevious()
、previous()
、previousIndex()
就可以獲得一個不可修改的列表,如果你想讓它可修改還需實現remove()
、set(E e)
、add(E e)
方法。
屬性
LinkedList
的主要屬性如下代碼所示:
//鏈表節點的個數
transient int size = 0;
//鏈表首節點
transient Node<E> first;
//鏈表尾節點
transient Node<E> last;
關於transient
關鍵字的最用可以查看我上次寫的ArrayList。
//內部靜態類
private static class Node<E> {
//當前節點元素值
E item;
//下一個節點
Node<E> next;
//上一個節點
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
構造函數
無參構造函數
如果不傳入參數,則使用默認構造方法創建LinkedList對象,如下:
public LinkedList() {
}
此時創建的是一個空的鏈表。
帶Collection對象的構造函數
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
首先會調用無參數的構造方法,然后調用addAll
方法將集合內元素全部加入到鏈表中,addAll
方法我們后面會詳細的分析。
從上述的倆個構造方法可以看出LinkedList是一個無界鏈表,不存在容量不足的問題。
添加元素
LinkedList主要提供addFirst
、addLast
、add
、addAll
等方法來實現元素的添加。下面我們一一來看:
//在鏈表首部添加元素
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
//將內部保存的首節點點賦值給f
final Node<E> f = first;
//創建新節點,新節點的next節點是當前的首節點
final Node<E> newNode = new Node<>(null, e, f);
//把新節點作為新的首節點
first = newNode;
//判斷是否是第一個添加的元素
//如果是將新節點賦值給last
//如果不是把原首節點的prev設置為新節點
if (f == null)
last = newNode;
else
f.prev = newNode;
//更新鏈表節點個數
size++;
//將集合修改次數加1
modCount++;
}
//在鏈表尾部添加元素
public void addLast(E e) {
linkLast(e);
}
void linkLast(E e) {
//將內部保存的尾節點賦值給l
final Node<E> l = last;
//創建新節點,新節點的prev節點是當前的尾節點
final Node<E> newNode = new Node<>(l, e, null);
//把新節點作為新的尾節點
last = newNode;
//判斷是否是第一個添加的元素
//如果是將新節點賦值給first
//如果不是把原首節點的next設置為新節點
if (l == null)
first = newNode;
else
l.next = newNode;
//更新鏈表節點個數
size++;
//將集合修改次數加1
modCount++;
}
//該方法和addLast方差不多,因為是無界的,所以添加元素總是會成功
public boolean add(E e) {
linkLast(e);
return true;
}
//該方法和上面add方法的區別是,該方法可以指定位置插入元素
public void add(int index, E element) {
//判斷是否越界
checkPositionIndex(index);
//如果index等於鏈表節點個數,就將元素添加到倆表尾部,否則調用linkBefore方法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//獲取指定位置的節點
Node<E> node(int index) {
//如果index小於size的一半,就從首節點開始遍歷,一直獲取x的下一個節點
//如果index大於或等於size的一半,就從尾節點開始遍歷,一直獲取x的上一個節點
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//將元素插入到指定節點前
void linkBefore(E e, Node<E> succ) {
assert succ != null;
//拿到succ的上一節點
final Node<E> pred = succ.prev;
//創建新節點
final Node<E> newNode = new Node<>(pred, e, succ);
//將新節點作為succ的上一節點
succ.prev = newNode;
//判斷succ是否是首節點
//如果是將新節點作為新的首節點
//如果不是將新節點作為pred的下一節點
if (pred == null)
first = newNode;
else
pred.next = newNode;
//更新鏈表節點個數
size++;
//將集合修改次數加1
modCount++;
}
//將集合內的元素依次插入index位置后
public boolean addAll(int index, Collection<? extends E> c) {
//判斷是否越界
checkPositionIndex(index);
//將集合轉換為數組
Object[] a = c.toArray();
int numNew = a.length;
//判斷數組長度是否為0,為0直接返回false
if (numNew == 0)
return false;
//pred上一個節點,succ當前節點
Node<E> pred, succ;
//判斷index位置是否等於鏈表元素個數
//如果等於succ賦值為null,pred賦值為當前鏈表尾節點last
//如果不等於succ賦值為index位置的節點,pred賦值為succ的上一個節點
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//循環數組
for (Object o : a) {
E e = (E) o;
//創建新節點
Node<E> newNode = new Node<>(pred, e, null);
//如果上一個節點為null,把新節點作為新的首節點,否則pred的下一個節點為新節點
if (pred == null)
first = newNode;
else
pred.next = newNode;
//把新節點賦值給上一個節點
pred = newNode;
}
//如果index位置的節點為null,把pred作業尾節點
//如果不為null,pred的下一節點為index位置的節點,succ的上一節點為pred
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
//更新鏈表節點個數
size += numNew;
//將集合修改次數加1
modCount++;
//因為是無界的,所以添加元素總是會成功
return true;
}
看到上面這么多種方式添加元素,其實本質只是三種方式,在鏈表的首部、尾部、中間位置添加元素。如下圖所示:
在鏈表首尾添加元素很高效,在中間添加元素比較低效,首先要找到插入位置的節點,在修改前后節點的指針。
刪除元素
LinkedList提供了remove
、removeFirst
、removeLast
等方法刪除元素。
public boolean remove(Object o) {
//因為LinkedList允許存在null,所以需要進行null判斷
if (o == null) {
//從首節點開始遍歷
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//調用unlink方法刪除指定節點
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//刪除指定節點
E unlink(Node<E> x) {
//獲取x節點的元素,以及它上一個節點,和下一個節點
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//如果x的上一個節點為null,說明是首節點,將x的下一個節點設置為新的首節點
//否則將x的上一節點設置為next,將x的上一節點設為null
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//如果x的下一節點為null,說明是尾節點,將x的上一節點設置新的尾節點
//否則將x的上一節點設置x的上一節點,將x的下一節點設為null
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//將x節點的元素值設為null,等待垃圾收集器收集
x.item = null;
//鏈表節點個數減1
size--;
//將集合修改次數加1
modCount++;
//返回刪除節點的元素值
return element;
}
//刪除指定位置的節點,其實和上面的方法差不多
//通過node方法獲得指定位置的節點,再通過unlink方法刪除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//刪除首節點
public E remove() {
return removeFirst();
}
//刪除首節點
public E removeFirst() {
final Node<E> f = first;
//如果首節點為null,說明是空鏈表,拋出異常
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//刪除首節點
private E unlinkFirst(Node<E> f) {
//首節點的元素值
final E element = f.item;
//首節點的下一節點
final Node<E> next = f.next;
//將首節點的元素值和下一節點設為null,等待垃圾收集器收集
f.item = null;
f.next = null; // help GC
//將next設置為新的首節點
first = next;
//如果next為null,說明說明鏈表中只有一個節點,把last也設為null
//否則把next的上一節點設為null
if (next == null)
last = null;
else
next.prev = null;
//鏈表節點個數減1
size--;
//將集合修改次數加1
modCount++;
//返回刪除節點的元素值
return element;
}
//刪除尾節點
public E removeLast() {
final Node<E> l = last;
//如果首節點為null,說明是空鏈表,拋出異常
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
//尾節點的元素值
final E element = l.item;
//尾節點的上一節點
final Node<E> prev = l.prev;
//將尾節點的元素值和上一節點設為null,等待垃圾收集器收集
l.item = null;
l.prev = null; // help GC
//將prev設置新的尾節點
last = prev;
//如果prev為null,說明說明鏈表中只有一個節點,把first也設為null
//否則把prev的下一節點設為null
if (prev == null)
first = null;
else
prev.next = null;
//鏈表節點個數減1
size--;
//將集合修改次數加1
modCount++;
//返回刪除節點的元素值
return element;
}
刪除和添加一樣,其實本質只有三種方式,即刪除首部、尾部、中間節點。如下圖(圖片來自網絡)所示:
和添加一樣,首尾刪除很高效,刪除中間元素比較低效要先找到節點位置,再修改前后指針。
獲取元素
LinkedList提供了get
、getFirst
、getLast
等方法獲取節點元素值。
//獲取指定位置的元素值
public E get(int index) {
//判斷是否越界
checkElementIndex(index);
//直接調用node方法獲取指定位置的節點,並反回其元素值
return node(index).item;
}
//獲取鏈表首節點的元素值
public E getFirst() {
final Node<E> f = first;
//判斷是否是空鏈表,如果是拋出異常,否則直接返回首節點的元素值
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//獲取鏈表尾節點的元素值
public E getLast() {
final Node<E> l = last;
//判斷是否是空鏈表,如果是拋出異常,否則直接返回尾節點的元素值
if (l == null)
throw new NoSuchElementException();
return l.item;
}
更新指定位置節點的元素值
public E set(int index, E element) {
//判斷是否越界
checkElementIndex(index);
//指定位置的節點
Node<E> x = node(index);
E oldVal = x.item;
//設置新值
x.item = element;
//返回老值
return oldVal;
}
關於隊列的操作
LinkedList可以作為FIFO(First In First Out)的隊列,也就是先進先出的隊列使用,以下是關於隊列的操作。
//獲取隊列的第一個元素,如果為null會返回null
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//獲取隊列的第一個元素,如果為null會拋出異常
public E element() {
return getFirst();
}
//獲取隊列的第一個元素,如果為null會返回null
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//獲取隊列的第一個元素,如果為null會拋出異常
public E remove() {
return removeFirst();
}
//將元素添加到隊列尾部
public boolean offer(E e) {
return add(e);
}
關於雙端隊列的操作
雙端列隊可以作為棧使用,棧的特性是LIFO(Last In First Out),也就是后進先出。所以作為棧使用也很簡單,添加和刪除元素都只操作隊列的首節點即可。
//將元素添加到首
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//將元素添加到尾部
public boolean offerLast(E e) {
addLast(e);
return true;
}
//獲取首部的元素值
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//獲取尾部的元素值
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//刪除首部,如果為null會返回null
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//刪除尾部,如果為null會返回null
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//將元素添加到首部
public void push(E e) {
addFirst(e);
}
//刪除首部,如果為null會拋出異常
public E pop() {
return removeFirst();
}
//刪除鏈表中元素值等於o的第一個節點,其實和remove方法是一樣的,因為內部還是調用的remove方法
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//刪除鏈表中元素值等於o的最后一個節點
public boolean removeLastOccurrence(Object o) {
//因為LinkedList允許存在null,所以需要進行null判斷
if (o == null) {
//和remove方法的區別它是從尾節點往前遍歷
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
//調用unlink方法刪除指定節點
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
其他方法
//判斷元素是否存在於鏈表中
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//從前往后查找返回節點元素值等於o的位置,不存在返回-1
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
//該方法和上面indexOf方法相反,它是從后往前查找返回節點元素值等於o的位置,不存在返回-1
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
//克隆函數,返回LinkedList的克隆對象
public Object clone() {
LinkedList<E> clone = superClone();
// 將新建LinkedList置於最初狀態
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// 將鏈表中所有節點的數據都添加到克隆對象中
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
//返回LinkedList節點單元素值的Object數組
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
//將鏈表中所有節點的元素值添加到object數組中
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
// 返回LinkedList的模板數組。所謂模板數組,即可以將T設為任意的數據類型
public <T> T[] toArray(T[] a) {
//如果a的長度小於LinkedList節點個數,說明a不能容納LinkedList的所有節點元素值
//則新建一個數組,數組大小為LinkedList節點個數,並賦值給a
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
// 將鏈表中所有節點的元素值都添加到數組a中
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
//將LinkedList中的數據寫入到輸入流中,先寫容量,再寫數據
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
//從輸入流中讀取數據,一樣是先讀容量,再讀數據
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
//從輸入流中將元素值逐個添加到鏈表中
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
總結
- LinkedList自己實現了序列化和反序列化,因為它實現了writeObject和readObject方法。
- LinkedList是一個以雙向鏈表實現的List。
- LinkedList還是一個雙端隊列,具有隊列、雙端隊列、棧的特性。
- LinkedList在首部和尾部添加、刪除元素效率高效,在中間添加、刪除元素效率較低。
- LinkedList雖然實現了隨機訪問,但是效率低效,不建議使用。
- LinkedList是線程不安全的。