上一章進行了ArrayList源碼分析,這一章分析一下另一個重要的List集合LinkedList。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
LinkedList與ArrayList對比發現:
- 它們繼承的基類不同,LinkedList繼承自AbstractSequentialList基類,AbstractSequentialList是AbstractList子類,這個類后面再說。
- LinkedList實現了Deque接口,代表它是一個隊列,準確地說它是一個雙端隊列。
- LinkedList沒有實現RandomAccess可隨機訪問標記接口,表示使用LinkedList的get(int index)獲取集合中元素的方法效率非常低。
一. Queue隊列接口
隊列是一種FIFO(先入先出)的數據結構,和它相對應的是一種叫做棧(LIFO后入先出)的數據結構。
1.1 棧
對于棧來說,我們想一想它應該有哪些方法?
- void push(E e); 向棧頂添加元素。
- E pop(); 移除棧頂元素,并返回它。
- E peek(); 查看棧頂元素。
- boolean isEmpty(); 棧是不是為空。
- int size(); 棧中元素的數量。
要實現一個棧,實現這5個方法就可以了。
1.2 隊列
隊列與棧的方法應該差不多,只不過每次添加的時候,都是向隊列尾新添元素,而不是隊列頭。
- boolean offer(E e); 向隊列尾添加元素。
- E poll();移除隊列頭元素,并返回它。
- E peek(); 查看隊列頭元素。
- boolean isEmpty(); 隊列是不是為空。
- int size(); 隊列中元素的數量。
public interface Queue<E> extends Collection<E> {
// 向隊列末尾新添加元素,返回true表示添加成功
// 不會返回false,因為添加失敗直接拋出IllegalStateException異常。
// 一般調用offer方法實現。
boolean add(E e);
// 向隊列末尾新添加元素,返回true表示添加成功,返回false,添加失敗
boolean offer(E e);
// 這個與Collection中的remove方法不一樣,因為Collection中的remove方法都要提供一個元素或者集合,用于刪除。
// 這里不穿任何參數,就是代表刪除隊列第一個元素(即隊列頭),并返回它
// 還需要注意的時,如果隊列是空的,即隊列頭是null,這個方法會拋出NoSuchElementException異常。
E remove();
// 這個方法也是刪除隊列第一個元素(即隊列頭),并返回它
// 但是它和remove()方法不同的時,如果隊列是空的,即隊列頭是null,它不會拋出異常,而是會返回null。
E poll();
// 查看隊列頭的元素,如果隊列是空的,就拋出異常
E element();
// 查看隊列頭的元素。如果隊列是空的,不會拋出異常,而是返回null
E peek();
}
可以看出繼承自Collection接口,那么size()和isEmpty()方法都由Collection接口提供,但是Queue接口還提供了是三個好像重復的方法。
- 向隊列尾添加元素的方法:add(E e)與offer(E e)。區別就是隊列是滿的,添加失敗時,add方法會拋出異常,而offer方法只會返回false。
- 移除隊列頭元素的方法:remove()與poll()。區別就是隊列為空的時候,remove方法會拋出異常,poll方法只會返回null。
- 查看隊列頭元素的方法:element()與peek()。區別就是隊列為空的時候,element方法會拋出異常,peek方法只會返回null。
下面是AbstractQueue中的實現
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {
protected AbstractQueue() {
}
// 直接調用offer方法來實現,如果隊列是滿的,添加失敗,
// 則拋出IllegalStateException異常
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
// 直接調用poll方法來實現,如果隊列是空的,移除元素失敗,
// 則拋出NoSuchElementException異常
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
// 直接調用peek方法來實現,如果隊列是空的,查看元素失敗,
// 則拋出NoSuchElementException異常
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
......
}
二. 雙端隊列
它與普通隊列相比較,它既可以在隊列頭添加元素,也可以在隊列尾添加元素;既可以在隊列頭刪除元素,也可以在隊列尾刪除元素。
注意一下,因為雙端隊列特性,所以它很容易實現一個棧,也就是說它本身可以當做棧使用。
根據雙端隊列的特性,它比普通隊列應該多了三個方法。
- boolean offerFirst(E e); 向隊列頭添加元素。
- boolean offerLast(E e); 向隊列尾添加元素。
- E pollFirst();移除隊列頭元素,并返回它。
- E pollLast();移除隊列尾元素,并返回它。
- E peekFirst(); 查看隊列頭元素。
- E peekLast(); 查看隊列尾元素。
- boolean isEmpty(); 隊列是不是為空。
- int size(); 隊列中元素的數量。
public interface Deque<E> extends Queue<E> {
// 向隊列頭添加元素
void addFirst(E e);
// 向隊列末尾新添加元素
void addLast(E e);
// 向隊列頭添加元素,和addFirst(E e)作用一樣,就是直接調用addFirst(E e)方法來實現。
boolean offerFirst(E e);
// 向隊列末尾新添加元素,和addLast(E e)作用一樣,就是直接調用addLast(E e)方法來實現。
boolean offerLast(E e);
// 刪除隊列第一個元素(即隊列頭),并返回它, 如果隊列是空的,這個方法會拋出NoSuchElementException異常。
// 注,與Queue接口中remove()作用一樣,remove()方法就是調用removeFirst()方法來實現的
E removeFirst();
// 刪除隊列最后一個元素(即隊列尾),并返回它, 如果隊列是空的,這個方法會拋出NoSuchElementException異常。
E removeLast();
// 刪除隊列第一個元素(即隊列頭),并返回它, 如果隊列是空的,它不會拋出異常,而是會返回null。
// 注,與Queue接口中poll()作用一樣,
E pollFirst();
// 刪除隊列最后一個元素(即隊列尾),并返回它, 如果隊列是空的,它不會拋出異常,而是會返回null。
E pollLast();
// 查看隊列頭的元素,如果隊列是空的,就拋出異常
// 注,與Queue接口中element()作用一樣,
E getFirst();
// 查看隊列尾的元素,如果隊列是空的,就拋出異常
E getLast();
// 查看隊列頭的元素。如果隊列是空的,不會拋出異常,而是返回null
E peekFirst();
// 查看隊列尾的元素。如果隊列是空的,不會拋出異常,而是返回null
E peekLast();
// 從隊列頭都開始遍歷,找到與o相等的第一個元素刪除它,并返回true,如果沒找到就返回false,最多只刪除一個元素
// 注,與Collection中remove(Object o)方法作用一樣
boolean removeFirstOccurrence(Object o);
// 從隊列尾都開始遍歷,找到與o相等的第一個元素刪除它,并返回true,如果沒找到就返回false,最多只刪除一個元素
boolean removeLastOccurrence(Object o);
// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// *** Stack methods ***
// 向棧頂添加元素。與addFirst(E e)方法作用一樣
void push(E e);
// 移除棧頂元素,并返回它。如果棧為空的話,會拋出NoSuchElementException異常
// 注,與removeFirst()方法一樣
E pop();
// *** Collection methods ***
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
可以看出定義的接口中的方法比我們預計的多得多,主要是添加了一些隊列為空時,獲取元素會拋出異常的方法,還順便定義了棧的方法,因為雙端隊列很容易實現一個棧的功能。
雙端隊列Deque與普通隊列Queue相比較,就是多了從隊列頭插入,從隊列尾刪除,從隊列尾查看的功能。
三. AbstractSequentialList抽樣類
AbstractSequentialList這個類表示它的子類是使用鏈表這種數據結構來存儲集合元素的,而不是使用數組這種數據結構。這有什么不同呢?
- 數組的插入和刪除的效率都不高,因為可能涉及到數組元素的移動。但是訪問效率非常高,它支持隨機訪問,就是通過數組的下標直接獲取對應的元素。
- 鏈表的插入和刪除的效率都很高,因為只需要改變元素之間指向就可以了。但是訪問效率不高,它不支持隨機訪問,必須從鏈表頭或者鏈表尾開始一次訪問。
還記得我們在AbstractList方法中,怎么實現迭代器的么?
使用一個cursor屬性來記錄索引位置,然后通過調用List集合的get(int index)來獲取對應的元素。這里就不行了,因為通過get(int index)方法獲取集合元素的效率非常低。
而遍歷鏈表的方式就是獲取鏈表中一個元素,然后通過指向下一個元素的引用,不斷獲取下一個元素,直到為空,表示已經到了鏈表尾,而不是通過索引的方式。
所以我們思考一下AbstractSequentialList會做哪些事情。
- 將獲取迭代器的方法設置成abstract抽樣方法,強制子類提供迭代器方法,因為不能用索引這種低效率的方式獲取元素,所以強制子類去實現。
// 調用listIterator方法,返回一個迭代器
public Iterator<E> iterator() {
return listIterator();
}
// 子類必須復寫這個方法,提供一個ListIterator迭代器。
public abstract ListIterator<E> listIterator(int index);
- List集合可以通過索引得到集合中的元素,AbstractSequentialList集合也必須支持這種方式,雖然效率低。這時就可以通過ListIterator迭代器實現對應方法。
這里就與AbstractList中內部迭代器ListIterator類不同,AbstractList中迭代器是通過調用AbstractList中get(int index)和set(int index, E element)方法來實現對應功能的,所以AbstractList子類必須復寫這些方法。
而AbstractSequentialList是通過迭代器來實現本AbstractSequentialList對應方法,所以子類必須實現一個自定義迭代器。
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
protected AbstractSequentialList() {
}
// 調用迭代器listIterator獲取
public E get(int index) {
try {
// 迭代器會先根據index值,從鏈表頭開始遍歷,直到移動到index位置,將元素返回,所以效率不高。
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// 調用迭代器listIterator的set設置
public E set(int index, E element) {
try {
ListIterator<E> e = listIterator(index);
E oldVal = e.next();
e.set(element);
return oldVal;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// 調用迭代器listIterator的add方法添加元素
public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// 調用迭代器listIterator的remove方法移除元素
public E remove(int index) {
try {
ListIterator<E> e = listIterator(index);
E outCast = e.next();
e.remove();
return outCast;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// Bulk Operations
public boolean addAll(int index, Collection<? extends E> c) {
try {
boolean modified = false;
ListIterator<E> e1 = listIterator(index);
Iterator<? extends E> e2 = c.iterator();
while (e2.hasNext()) {
e1.add(e2.next());
modified = true;
}
return modified;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// 會調用listIterator方法,返回一個迭代器
public Iterator<E> iterator() {
return listIterator();
}
// 子類必須復寫這個方法,提供一個ListIterator迭代器。
public abstract ListIterator<E> listIterator(int index);
}
四. 單向鏈表和雙向鏈表
4.1 單向鏈表
簡單的說,元素除了包含本身的數據item,還有一個指向下一個元素的引用next。數據結構就像這樣:
class Node<E> {
E item;
Node<E> next;
Node( E element, Node<E> next) {
this.item = element;
this.next = next;
}
}
然后我們在看看單向鏈表插入和刪除。
- 插入:單向鏈表的插入,我們只需要改變兩個引用就可以了。
private void insert(Node<E> prevNode, Node<E> node, Node<E> newNode) {
// prevNode表示插入點前一個元素
// node表示插入點元素
// newNode表示要添加的元素,將它放入插入點(即前一個元素的next指向)
// 并將newNode的next指向原來元素node
if (prevNode != null) prevNode.next = newNode;
newNode.next = node;
}
在鏈表node元素前添加一個元素,就是將node元素前一個元素的next指向新元素newNode,再將新元素newNode的next指向node元素,這樣就把新元素newNode插入到鏈表中了。
注意要做一下前元素非空判斷,如果前元素為空表示插入點是鏈表頭。
根據我的經驗,先不考慮null的情況,改變對應引用,這里就是prevNode.next = newNode,newNode.next = node。然后我們再看看那些需要考慮null的情況。
比如這里prevNode就需要考慮null情況,否則會發生空指針異常。prevNode為空其實表示在鏈表頭。newNode是不允許為空。而node是不是為空對我們程序沒有任何影響。
- 刪除:單向鏈表的刪除,也只需要改變兩個引用就可以了。
private void delete(Node<E> prevNode, Node<E> node) {
// prevNode表示被刪除元素的前一個元素
// node表示被刪除的元素
if (prevNode != null) prevNode.next = node.next;
node.next = null;
}
刪除一個單向鏈表元素,就是將它的前一個元素的next指向它的next,這樣就在整個鏈表中查找不到這個元素了,然后將它的next設置為null。
當前一個元素為null,表示被刪除元素是鏈表頭,那么需要將表頭的next指向被刪除元素的next,這里沒有體現。
4.2 雙向鏈表
與單向鏈表相比,它的元素多了一個指向上一個元素的引用prev。數據結構就像這樣:
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;
}
}
然后我們在看看雙向鏈表插入和刪除。
- 插入:雙向鏈表的插入,我們需要改變四個引用。
private void insert(Node<E> node, Node<E> newNode) {
// node表示插入點元素
// newNode表示要添加的元素,將它插入到node元素之前
// 將node前一個元素的next指向新元素newNode
if(node.prev != null) node.prev.next = newNode;
// 將新元素newNode的prev指向node前一個元素
newNode.prev = node.prev;
// 將node的prev指向新元素newNode,現在node的前一個元素變成新元素newNode
node.prev = newNode;
// 將新元素的next指向node,所以新元素的下一個元素是node
newNode.next = node;
}
要在元素node前插入一個新元素newNode。那么就需要四步:
- 將node前一個元素的next指向新元素newNode
- 將新元素newNode的prev指向node前一個元素
- node元素的prev指向新元素
- 新元素newNode的next指向node
- 刪除:雙向鏈表的刪除,也需要改變四個引用。
private void delete(Node<E> node) {
// node表示要刪除的元素
// 將node前一個元素的next指向node下一個元素
if (node.prev != null) node.prev.next = node.next;
// 將node下一個元素的pre指向node前一個元素
if (node.next != null) node.next.prev = node.prev;
// 將node的prev和next都置位null
node.prev = null;
node.next = null;
}
注意只考慮本節點元素情況,沒有考慮鏈表頭的賦值。
五. LinkedList 類
5.1 成員屬性
// 集合數量
transient int size = 0;
// 雙向鏈表的表頭
transient Node<E> first;
// 雙向鏈表的表尾
transient Node<E> last;
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;
}
}
通過一個雙向鏈表來記錄集合中的元素。
5.2 構造函數
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList的構造函數比較簡單,因為它不用想ArrayList那樣,要確定初始數組的長度。
5.3 添加元素
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++;
}
向鏈表尾添加一個新元素newNode,要進行以下幾個步驟:
- 將鏈表尾last賦值給變量l,因為表尾last要指向新元素newNode
- 創建新元素newNode,根據Node的構造函數,我們知道新元素newNode的prev指向l(即表尾),next還是為null。
- 將表尾last指向新元素newNode
- 將原表尾l的next指向新元素,這時要考慮一種情況,原表尾l為null,即整個鏈表是空的,那么這個時候,我們只需要將表頭first也指向新元素newNode就可以了。
- 集合數量size加1,以及modCount自增表示集合已經修改了。
注意,這里好像只改變了三個應用,缺少了新元素newNode下一個元素的prev指向新元素newNode。這是因為在表尾,不存在下一個元素。
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
在鏈表指定索引位置插入元素,如果index等于size,表示在表尾插入元素,直接調用linkLast(element)方法,否則先調用node(index)方法,找到index索引對應元素node,并將要添加元素element插入到元素node之前。
Node<E> node(int index) {
// 如果index小于集合數量的一半,那么從表頭開始遍歷,一直到index位置。
// 否則從表尾開始遍歷,一直到index位置。這樣我們每次最多遍歷size/2的次數。
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;
}
}
這里用了個巧妙方法,先判斷index在集合的前一半還是后一半,決定從鏈表頭還是鏈表尾遍歷。
void linkBefore(E e, Node<E> succ) {
// e表示新添加的元素
// succ表示被插入的元素(即新元素插入到這個元素之前)
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
在succ元素之前插入新元素e,要進行以下幾個步驟:
- 將元素succ的前一個元素賦值給變量pred
- 創建新元素newNode。 新元素newNode的prev指向pred,next指向succ。
- 將元素succ的prev指向新元素newNode。
- 將元素pred的next指向新元素newNode。但是考慮一種情況,pred為null,即元素succ就是鏈表頭,那么新添加元素就變成新表頭了,first = newNode。
- 集合數量size加1,以及modCount自增表示集合已經修改了。
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
// pred表示index位置前一個元素,succ表示index位置元素
Node<E> pred, succ;
if (index == size) {
// 當index == size時,index位置的元素為null,它的前一個元素是表尾last元素
succ = null;
pred = last;
} else {
// 通過ode(index)方法,查找index位置元素
succ = node(index);
pred = succ.prev;
}
// 遍歷要插入集合c的元素,將它們插入到本集合中
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 將新元素的prev指向前一個元素pred
Node<E> newNode = new Node<>(pred, e, null);
// pred為空表示,插入點在表頭,所以將新元素設置為表頭
if (pred == null)
first = newNode;
else
// 將前一個元素pred的next指向新元素newNode
pred.next = newNode;
// pred指向新元素,然后繼續遍歷
pred = newNode;
}
// pred現在表示插入集合元素最后一個元素
// succ為空表示在表尾插入集合,那么插入集合中最后一個元素就成為新的表尾
if (succ == null) {
last = pred;
} else {
// 將插入集合中最后一個元素和插入點index位置元素進行聯系。
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
在集合index位置前插入一個集合c中所有元素。可以這么做:
- 先找到index位置元素succ,和它前一個元素pred。
- 遍歷集合c中元素,將它們插入到元素pred之后,即新元素newNode.prev = pred, pred.next = newNode。然后將 pred = newNode; 再依次遍歷集合c。
- 遍歷完成之后,pred就指向集合c最后一個添加的元素。這時就要讓它和index位置元素發生聯系。
當然這個過程中還要考慮表頭和表尾的改變。
5.4 刪除元素
public boolean remove(Object o) {
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;
}
遍歷整個集合,找到與o相同元素,調用unlink方法刪除這個元素,如果沒有找打,就返回false。
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
這個方法和我們前面寫的雙向鏈表刪除方法一樣。主要就是
- 被刪除元素x的前一個元素的next指向被刪除元素后一個元素。
- 被刪除元素x后一個元素的prev指向被刪除元素x前一個元素。
- 最后將刪除元素x的prev與next都設置為null。
- 當然要注意下表頭和表尾的判斷,如果被刪除元素x的prev為null,表示x是表頭,那么就要將表頭first指向元素x的下一個元素。如果被刪除元素x的next為null,表示x是表尾,那么就要將表尾last指向元素x前一個元素。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
通過node(index)方法,獲取index索引對應元素,然后調用unlink(Node<E> x) 方法刪除這個索引。
public void clear() {
// 遍歷鏈表,將鏈表中的引用都置位null,方便垃圾回收器釋放內存
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
將鏈表中元素的引用都置位null,方便垃圾回收器回收。
注 boolean removeAll(Collection<?> c)與boolean retainAll(Collection<?> c)都是使用AbstractCollection抽樣類的默認實現。也就是通過迭代器Iterator來刪除集合中元素。
5.5 替換元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
替換元素非常簡單,通過node(index)查找出元素,將元素中數據賦值給oldVal,再將新數據element設置到元素中,最后返回老數據oldVal。
5.5 查找元素
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;
}
從表頭開始遍歷,查找第一個與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;
}
從表尾開始遍歷,查找第一個與o值相等元素,返回對應索引,如果沒找到就返回-1 。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
通過node(index)方法找到對應索引的元素,然后返回元素的值。
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
返回LinkedList內部的一個迭代器。這個類我們之后會詳細介紹。
5.6 其他重要方法
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
將集合轉成一個Object[]數組,先創建一個長度為集合數量size的Object[]數組,然后遍歷鏈表,將元素中數據item存放到數組中。
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
將集合轉成T類型的數組。如果數組a的長度小于集合數量size,那么就要創建一個新數組,再賦值給a,然后遍歷鏈表,將元素中數據item存放到數組a中。
這里有個很詭異的地方,就是if (a.length > size)這個判斷。我們知道數組a中 0 -- size-1 位置的元素都是集合中的,那么從size位置開始之后的元素都是數組a原有的元素,這里不知道為什么單單將size位置元素置位null。
六. LinkedList內部類ListItr
6.1 成員屬性
// 代表當前遍歷到的元素
private Node<E> lastReturned;
// 表示迭代器開始的元素
private Node<E> next;
// 表示元素next在鏈表中的位置,與next是相對應的。
private int nextIndex;
private int expectedModCount = modCount;
6.2 構造函數
ListItr(int index) {
// 先通過LinkedList的node方法,查找index索引位置對于的元素,賦值給next
next = (index == size) ? null : node(index);
// 將index賦值給 nextIndex
nextIndex = index;
}
6.3 正向遍歷集合
public boolean hasNext() {
return nextIndex < size;
}
當nextIndex小于集合數量size,說明集合還有元素沒有遍歷到。
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
將next賦值給lastReturned,再將next指向它的下一個元素,然后將nextIndex自增,最后返回當前元素lastReturned的數據item。
6.4 反向遍歷
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
這里做了一個處理,還記得在ListItr構造函數中,如果index == size,那么next就賦值為null,所以這里當next == null就從表尾開始向前遍歷。
6.5 返回索引
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
這個已經在AbstractList中的詳細介紹過了。
6.6 操作集合的方法
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
// 將當前元素下一個元素賦值給lastNext
Node<E> lastNext = lastReturned.next;
// 調用LinkedList集合的unlink方法,刪除當前元素
unlink(lastReturned);
// 如果next == lastReturned,表示反向遍歷。
// 將next指向lastNext,因為lastNext的前一個就是原lastReturned前一個元素,所以不會有遺漏
if (next == lastReturned)
next = lastNext;
else
// 表示正向遍歷,那么刪除當前元素,只有一個影響,就是集合數量減少了。
// 而正向遍歷結束條件時nextIndex < size,所以要將nextIndex自減。
// 而反向遍歷是結束條件是nextIndex > 0,所以不需要處理
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
// 超級簡單,就是將當前元素的數據item設置成e
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
// 如果next == null,就在鏈表尾插入元素e
if (next == null)
linkLast(e);
else
// 不然就在next元素之前插入元素e
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
總結
LinkedList不僅是一個List集合,它還是一個隊列,或者說是雙端隊列。
棧
棧是一個后入先出(LIFO)的數據結構,主要是三個方法:
- void push(E e); 向棧頂添加元素。
- E pop(); 移除棧頂元素,并返回它。
- E peek(); 查看棧頂元素。
隊列
隊列是一個先入先出(FIFO)的數據結構,主要是三個方法:
- boolean offer(E e); 向隊列尾添加元素。
- E poll();移除隊列頭元素,并返回它。
- E peek(); 查看隊列頭元素。
雙端隊列
雙端隊列與普通隊列做比較,它既可以在隊列頭添加元素,也可以在隊列尾添加元素;既可以在隊列頭刪除元素,也可以在隊列尾刪除元素。
它的主要方法有六個:
- boolean offerFirst(E e); 向隊列頭添加元素。
- boolean offerLast(E e); 向隊列尾添加元素。
- E pollFirst();移除隊列頭元素,并返回它。
- E pollLast();移除隊列尾元素,并返回它。
- E peekFirst(); 查看隊列頭元素。
- E peekLast(); 查看隊列尾元素。
AbstractSequentialList抽樣類
AbstractSequentialList這個類表示它的子類是使用鏈表這種數據結構來存儲集合元素的,而不是使用數組這種數據結構。也就是說它沒有可隨機訪問能力。
單向鏈表和雙向鏈表
注意一下單向鏈表和雙向鏈表的插入和刪除。
單向鏈表的插入和刪除最多改變兩個引用,而雙向鏈表的插入和刪除最多改變四個引用。
LinkedList 類
使用first表示雙向鏈表的表頭,使用last表示雙向鏈表的表尾。