將分析以下內(nèi)容
- 字段
- 構(gòu)造函數(shù)
- 研究插入和刪除
- 研究查詢
1.首先來看一下LinkedList里面的屬性
這個(gè)是數(shù)組存儲元素的總數(shù),相信size()方法大家都用過
transient int size = 0;
下面兩個(gè)分別是首節(jié)點(diǎn)和為節(jié)點(diǎn)
transient Node<E> first;
transient Node<E> last;
這里順便介紹一下數(shù)組中存儲的基本單位——節(jié)點(diǎn)(包括節(jié)點(diǎn)內(nèi)容,上一節(jié)點(diǎn),下一節(jié)點(diǎn))
private static class Node<E> {
E item; //節(jié)點(diǎn)存儲的內(nèi)容
Node<E> next; //下一節(jié)點(diǎn)
Node<E> prev; //上一節(jié)點(diǎn)
//三個(gè)參數(shù)的構(gòu)造函數(shù)
Node(Node<E> prev, E element, Node<E> next) {...}
}
1.接下來看一下LinkedList里面的構(gòu)造函數(shù)
如果是無參的構(gòu)造函數(shù),僅僅只會(huì)進(jìn)行初始化
public LinkedList() {}
在介紹下一個(gè)構(gòu)造函數(shù)之前,我們先來看一看LinkedList是如何獲取元素的
public E get(int index) {
checkElementIndex(index); //判斷角標(biāo)是否越界
return node(index).item;
}
這里我們可以看到需要調(diào)用一個(gè)node()方法,并且我們可以知道該方法返回的是一個(gè)Node對象,接下來我們就看一看該方法
Node<E> node(int index) { //index=34; size=100
//判斷查詢索引在數(shù)組中點(diǎn)左邊還是右邊
//左邊:從第一個(gè)節(jié)點(diǎn)開始遍歷
//右邊:從最后一個(gè)節(jié)點(diǎn)開始遍歷
if (index < (size >> 1)) { //index(34) < (size >> 1)(50)
Node<E> x = first; //從首節(jié)點(diǎn)開始遍歷,找到索引節(jié)點(diǎn)并返回
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()方法需要從首節(jié)點(diǎn)或是尾節(jié)點(diǎn)一個(gè)一個(gè)遍歷,所以說當(dāng)需要查詢普遍時(shí)不要用LinkedList
接下來讓我們來看一看LinkedList的另一構(gòu)造函數(shù)
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
該構(gòu)造函數(shù)傳遞了一個(gè)集合c,需要調(diào)用addAll()方法
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
addAll又需要調(diào)用addAll的另一個(gè)重載函數(shù),注意此時(shí)將size也就是當(dāng)前數(shù)組存儲元素的總數(shù)作為下面函數(shù)的index索引
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //檢查索引位置是否可做插入,也就比checkElementIndex多了size
Object[] a = c.toArray();
int numNew = a.length;
//長度為空的話什么也不做
if (numNew == 0)
return false;
//記錄插入的前置節(jié)點(diǎn)和后置節(jié)點(diǎn)
Node<E> pred, succ;
//如果未指定角標(biāo),將succ設(shè)置為空,將pred設(shè)置為最后一個(gè)節(jié)點(diǎn)
//否則將succ設(shè)置為指定索引處的節(jié)點(diǎn),pred設(shè)置為succ的上一節(jié)點(diǎn)
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//循環(huán)設(shè)置每個(gè)節(jié)點(diǎn)的前置和后置節(jié)點(diǎn)
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//創(chuàng)建新節(jié)點(diǎn),設(shè)置節(jié)點(diǎn)內(nèi)容和前置節(jié)點(diǎn)
Node<E> newNode = new Node<>(pred, e, null);
//如果上一節(jié)點(diǎn)為空,說明為首節(jié)點(diǎn),并設(shè)置first
//否則設(shè)置前置節(jié)點(diǎn)的后置節(jié)點(diǎn),使兩個(gè)節(jié)點(diǎn)相互關(guān)聯(lián)
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
//succ為空,說明未指定索引,設(shè)置尾節(jié)點(diǎn)
//否則關(guān)聯(lián)succ和所插入的最后一個(gè)節(jié)點(diǎn)
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew; //修改當(dāng)前數(shù)組存儲元素的總數(shù)
modCount++; //數(shù)組修改的次數(shù),該變量在AbstractList定義,不研究
return true;
}
為了方便理解,下面畫了一個(gè)圖,這是從中間插入集合,注意當(dāng)使用集合初始化LinkedList是直接從末尾差入,這里相當(dāng)于調(diào)用了addAll(int index, Collection<? extends E> c)方法,從指定位置插入集合
理解了addAll()再來看add()就非常簡單了
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++; //數(shù)組修改的次數(shù),該變量在AbstractList定義,不研究
}
這里不做細(xì)說了,和上面一樣,無非就是創(chuàng)建一個(gè)節(jié)點(diǎn),維護(hù)該節(jié)點(diǎn)和前置節(jié)點(diǎn)的關(guān)系
最后我們來看一下LinkedList是如何實(shí)現(xiàn)刪除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
這里可以看到刪除的具體邏輯是封裝在unlink里面的
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//刪除節(jié)點(diǎn)為首節(jié)點(diǎn),設(shè)置首節(jié)點(diǎn)為后置節(jié)點(diǎn)
//否則設(shè)置前置節(jié)點(diǎn)的后置節(jié)點(diǎn)為刪除節(jié)點(diǎn)的后置節(jié)點(diǎn)
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//刪除節(jié)點(diǎn)為尾節(jié)點(diǎn),設(shè)置后置節(jié)點(diǎn)為前置節(jié)點(diǎn)
//否則設(shè)置后置節(jié)點(diǎn)的前置節(jié)點(diǎn)為刪除節(jié)點(diǎn)的前置節(jié)點(diǎn)
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
下面是另一個(gè)刪除方法,這里不過細(xì)說
public boolean remove(Object o) {
//循環(huán)遍歷
//當(dāng)傳入對象為null,==判斷
//否則equals判斷
if (o == null) {
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;
}
總結(jié)——
通過以上我們明白了LinkedList為什么不適合做查詢
- get()方法會(huì)循環(huán)不斷調(diào)用node.next或node.prev以找到查詢元素,它沒有數(shù)組的索引,只能通過節(jié)點(diǎn)之間的關(guān)聯(lián)進(jìn)行查詢
為什么適合做插入和刪除
- 只需要修改節(jié)點(diǎn)之間的關(guān)聯(lián)