LinkedList 源碼分析,你想知道的都在這里
概述
LinkedList是一種可以在任何位置進(jìn)行高效地插入和移除操作的有序序列,它是基于雙向鏈表實(shí)現(xiàn)的,是線程不安全的,允許元素為null的雙向鏈表。
源碼分析
變量
/**
* 集合元素?cái)?shù)量
**/
transient int size = 0;
/**
* 指向第一個(gè)節(jié)點(diǎn)的指針
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 指向最后一個(gè)節(jié)點(diǎn)的指針
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
構(gòu)造方法
/**
* 無參構(gòu)造方法
*/
public LinkedList() {
}
/**
* 將集合c所有元素插入鏈表中
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
Node節(jié)點(diǎn)
private static class Node<E> {
// 值
E item;
// 后繼
Node<E> next;
// 前驅(qū)
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
因?yàn)橐粋€(gè)Node既有prev也有next,所以證明它是一個(gè)雙向鏈表。
添加元素
addAll(Collection c)
將集合c添加到鏈表,如果不傳index,則默認(rèn)是添加到尾部。如果調(diào)用addAll(int index, Collection c)方法,則添加到index后面。
/**
* 將集合添加到鏈尾
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
*
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
// 拿到目標(biāo)集合數(shù)組
Object[] a = c.toArray();
//新增元素的數(shù)量
int numNew = a.length;
//如果新增元素?cái)?shù)量為0,則不增加,并返回false
if (numNew == 0)
return false;
//定義index節(jié)點(diǎn)的前置節(jié)點(diǎn),后置節(jié)點(diǎn)
Node<E> pred, succ;
// 判斷是否是鏈表尾部,如果是:在鏈表尾部追加數(shù)據(jù)
//尾部的后置節(jié)點(diǎn)一定是null,前置節(jié)點(diǎn)是隊(duì)尾
if (index == size) {
succ = null;
pred = last;
} else {
// 如果不在鏈表末端(而在中間部位)
// 取出index節(jié)點(diǎn),并作為后繼節(jié)點(diǎn)
succ = node(index);
// index節(jié)點(diǎn)的前節(jié)點(diǎn) 作為前驅(qū)節(jié)點(diǎn)
pred = succ.prev;
}
// 鏈表批量增加,是靠for循環(huán)遍歷原數(shù)組,依次執(zhí)行插入節(jié)點(diǎn)操作
for (Object o : a) {
@SuppressWarnings("unchecked")
// 類型轉(zhuǎn)換
E e = (E) o;
// 前置節(jié)點(diǎn)為pred,后置節(jié)點(diǎn)為null,當(dāng)前節(jié)點(diǎn)值為e的節(jié)點(diǎn)newNode
Node<E> newNode = new Node<>(pred, e, null);
// 如果前置節(jié)點(diǎn)為空, 則newNode為頭節(jié)點(diǎn),否則為pred的next節(jié)點(diǎn)
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
// 循環(huán)結(jié)束后,如果后置節(jié)點(diǎn)是null,說明此時(shí)是在隊(duì)尾追加的
if (succ == null) {
// 設(shè)置尾節(jié)點(diǎn)
last = pred;
} else {
//否則是在隊(duì)中插入的節(jié)點(diǎn) ,更新前置節(jié)點(diǎn) 后置節(jié)點(diǎn)
pred.next = succ;
succ.prev = pred;
}
// 修改數(shù)量size
size += numNew;
//修改modCount
modCount++;
return true;
}
/**
* 取出index節(jié)點(diǎn)
*/
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果index 小于 size/2,則從頭部開始找
if (index < (size >> 1)) {
// 把頭節(jié)點(diǎn)賦值給x
Node<E> x = first;
for (int i = 0; i < index; i++)
// x=x的下一個(gè)節(jié)點(diǎn)
x = x.next;
return x;
} else {
// 如果index 大與等于 size/2,則從后面開始找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 檢測(cè)index位置是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 檢測(cè)index位置是否合法
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
假設(shè)我們要在index=2處添加{1,2}到鏈表中,圖解如下:
第一步:拿到index=2的前驅(qū)節(jié)點(diǎn) prev=ele1。
第二步:遍歷集合prev.next=newNode,并實(shí)時(shí)更新prev節(jié)點(diǎn)以便下一次。
遍歷:prev=newNode
第三步:將index=2的節(jié)點(diǎn)ele2接上:prev.next=ele2,ele2.prev=prev。
注意node(index)方法:尋找處于index的節(jié)點(diǎn),有一個(gè)小優(yōu)化,結(jié)點(diǎn)在前半段則從頭開始遍歷,在后半段則從尾開始遍歷,這樣就保證了只需要遍歷最多一半結(jié)點(diǎn)就可以找到指定索引的結(jié)點(diǎn)。
addFirst(E e)方法
將e元素添加到鏈表并設(shè)置其為頭節(jié)點(diǎn)(first)。
public void addFirst(E e) {
linkFirst(e);
}
//將e鏈接成列表的第一個(gè)元素
private void linkFirst(E e) {
final Node<E> f = first;
// 前驅(qū)為空,值為e,后繼為f
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//若f為空,則表明列表中還沒有元素,last也應(yīng)該指向newNode
if (f == null)
last = newNode;
else
//否則,前first的前驅(qū)指向newNode
f.prev = newNode;
size++;
modCount++;
}
- 拿到first節(jié)點(diǎn)命名為f。
- 新創(chuàng)建一個(gè)節(jié)點(diǎn)newNode設(shè)置其next節(jié)點(diǎn)為f節(jié)點(diǎn)。
- 將newNode賦值給first。
- 若f為空,則表明列表中還沒有元素,last也應(yīng)該指向newNode;否則,前first的前驅(qū)指向newNode。
addLast(E e)方法
將e元素添加到鏈表并設(shè)置其為尾節(jié)點(diǎn)(last)。
public void addLast(E e) {
linkLast(e);
}
/**
* 將e鏈接成列表的last元素
*/
void linkLast(E e) {
final Node<E> l = last;
// 前驅(qū)為前l(fā)ast,值為e,后繼為null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//最后一個(gè)節(jié)點(diǎn)為空,說明列表中無元素
if (l == null)
//first同樣指向此節(jié)點(diǎn)
first = newNode;
else
//否則,前l(fā)ast的后繼指向當(dāng)前節(jié)點(diǎn)
l.next = newNode;
size++;
modCount++;
}
過程與linkFirst()方法類似,這里略過。
add(E e)方法
在尾部追加元素e。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
// 前驅(qū)為前l(fā)ast,值為e,后繼為null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//最后一個(gè)節(jié)點(diǎn)為空,說明列表中無元素
if (l == null)
//first同樣指向此節(jié)點(diǎn)
first = newNode;
else
//否則,前l(fā)ast的后繼指向當(dāng)前節(jié)點(diǎn)
l.next = newNode;
size++;
modCount++;
}
add(int index, E element)方法
在鏈表的index處添加元素element.
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 在succ節(jié)點(diǎn)前增加元素e(succ不能為空)
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 拿到succ的前驅(qū)
final Node<E> pred = succ.prev;
// 新new節(jié)點(diǎn):前驅(qū)為pred,值為e,后繼為succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 將succ的前驅(qū)指向當(dāng)前節(jié)點(diǎn)
succ.prev = newNode;
// pred為空,說明此時(shí)succ為首節(jié)點(diǎn)
if (pred == null)
// 指向當(dāng)前節(jié)點(diǎn)
first = newNode;
else
// 否則,將succ之前的前驅(qū)的后繼指向當(dāng)前節(jié)點(diǎn)
pred.next = newNode;
size++;
modCount++;
}
linkLast方法上文有講。
linkBefore(E e, Node<E> succ)方法步驟:
- 拿到succ的前驅(qū)節(jié)點(diǎn)。
- 新new節(jié)點(diǎn):前驅(qū)為pred,值為e,后繼為succ : Node<>(pred, e, succ)。
- 將succ的前驅(qū)指向當(dāng)前節(jié)點(diǎn)。
- pred為空,說明此時(shí)succ為首節(jié)點(diǎn),first指向當(dāng)前節(jié)點(diǎn);否則,將succ之前的前驅(qū)的后繼指向當(dāng)前節(jié)點(diǎn)。
5、獲取/查詢?cè)?/h2>get(int index)方法
根據(jù)索引獲取鏈表中的元素。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 檢測(cè)index合法性
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 根據(jù)index 獲取元素
Node<E> node(int index) {
// assert isElementIndex(index);
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;
}
}
根據(jù)索引獲取鏈表中的元素。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 檢測(cè)index合法性
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 根據(jù)index 獲取元素
Node<E> node(int index) {
// assert isElementIndex(index);
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;
}
}
node方法上文有詳細(xì)講解,這里不做介紹。
getFirst()方法
獲取頭節(jié)點(diǎn)。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
getLast()方法
獲取尾節(jié)點(diǎn)。
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
6、刪除元素
remove(Object o)
根據(jù)Object對(duì)象刪除元素。
public boolean remove(Object o) {
// 如果o是空
if (o == null) {
// 遍歷鏈表查找 item==null 并執(zhí)行unlink(x)方法刪除
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
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) {
// assert x != null;
// 保存x的元素值
final E element = x.item;
//保存x的后繼
final Node<E> next = x.next;
//保存x的前驅(qū)
final Node<E> prev = x.prev;
//如果前驅(qū)為null,說明x為首節(jié)點(diǎn),first指向x的后繼
if (prev == null) {
first = next;
} else {
//x的前驅(qū)的后繼指向x的后繼,即略過了x
prev.next = next;
// x.prev已無用處,置空引用
x.prev = null;
}
// 后繼為null,說明x為尾節(jié)點(diǎn)
if (next == null) {
// last指向x的前驅(qū)
last = prev;
} else {
// x的后繼的前驅(qū)指向x的前驅(qū),即略過了x
next.prev = prev;
// x.next已無用處,置空引用
x.next = null;
}
// 引用置空
x.item = null;
size--;
modCount++;
// 返回所刪除的節(jié)點(diǎn)的元素值
return element;
}
- 遍歷鏈表查找 item==null 并執(zhí)行unlink(x)方法刪除。
- 如果前驅(qū)為null,說明x為首節(jié)點(diǎn),first指向x的后繼,x的前驅(qū)的后繼指向x的后繼,即略過了x。
- 如果后繼為null,說明x為尾節(jié)點(diǎn),last指向x的前驅(qū);否則x的后繼的前驅(qū)指向x的前驅(qū),即略過了x,置空x.next。
- 引用置空:x.item = null。
- 圖解。
remove(int index)方法
根據(jù)鏈表的索引刪除元素。
public E remove(int index) {
checkElementIndex(index);
//node(index)會(huì)返回index對(duì)應(yīng)的元素
return unlink(node(index));
}
E unlink(Node<E> x) 方法上文有詳解。
removeFirst()方法
刪除頭節(jié)點(diǎn)。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//取出首節(jié)點(diǎn)中的元素
final E element = f.item;
//取出首節(jié)點(diǎn)中的后繼
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
// first指向前first的后繼,也就是列表中的2號(hào)位
first = next;
//如果此時(shí)2號(hào)位為空,那么列表中此時(shí)已無節(jié)點(diǎn)
if (next == null)
//last指向null
last = null;
else
// 首節(jié)點(diǎn)無前驅(qū)
next.prev = null;
size--;
modCount++;
return element;
}
原理與添加頭節(jié)點(diǎn)類似。
removeLast()方法
刪除尾節(jié)點(diǎn)(last)
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
// 取出尾節(jié)點(diǎn)中的元素
final E element = l.item;
// 取出尾節(jié)點(diǎn)中的后繼
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
// last指向前l(fā)ast的前驅(qū),也就是列表中的倒數(shù)2號(hào)位
last = prev;
// 如果此時(shí)倒數(shù)2號(hào)位為空,那么列表中已無節(jié)點(diǎn)
if (prev == null)
// first指向null
first = null;
else
// 尾節(jié)點(diǎn)無后繼
prev.next = null;
size--;
modCount++;
// 返回尾節(jié)點(diǎn)保存的元素值
return element;
}
7、修改元素
修改元素比較簡(jiǎn)單,先找到index對(duì)應(yīng)節(jié)點(diǎn),然后對(duì)值進(jìn)行修改。
public E set(int index, E element) {
checkElementIndex(index);
// 獲取到需要修改元素的節(jié)點(diǎn)
Node<E> x = node(index);
// 保存之前的值
E oldVal = x.item;
// 執(zhí)行修改
x.item = element;
// 返回舊值
return oldVal;
}
8、與ArrayList的對(duì)比
優(yōu)點(diǎn):
- 不需要擴(kuò)容和預(yù)留空間,空間效率高。
- 增刪效率高。
缺點(diǎn):
- 隨機(jī)訪問時(shí)間效率低。
- 改查效率低。