ArrayList,陣列是順序儲存結構,儲存區間是連續的,佔用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為O(1),陣列的特點是定址容易,插入和刪除困難。 LinkedList使用連結串列作為儲存結構,連結串列是線性儲存結構,在記憶體上不是連續的一段空間,佔用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N),連結串列的特點是定址困難,插入和刪除容易。
在JDK1.7之前,LinkedList是採用雙向環形連結串列來實現的,在1.7及之後,Oracle將LinkedList做了優化,將環形連結串列改成了線性連結串列。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
複製程式碼
LinkedList繼承了AbstractSequentialList,實現了List,Deque,Cloneable,Serializable 介面
(1)繼承和實現
繼承AbstractSequentialList類,提供了相關的新增、刪除、修改、遍歷等功能。
實現List介面,提供了相關的新增、刪除、修改、遍歷等功能。
實現 Deque 介面,即能將LinkedList當作雙端佇列使用,可以用做佇列或者棧。
實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。
實現java.io.Serializable介面,LinkedList支援序列化,能通過序列化傳輸。
(2)執行緒安全
LinkedList是非同步的,即執行緒不安全,如果有多個執行緒同時訪問LinkedList,可能會丟擲ConcurrentModificationException異常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
複製程式碼
(3)節點Node結構:
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;
}
}
複製程式碼
(4)addAll方法:
//addAll ,在尾部批量增加
複製程式碼
public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//以size為插入下標,插入集合c中所有元素 } //以index為插入下標,插入集合c中所有元素 public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index);//檢查越界 [0,size] 閉區間
Object[] a = c.toArray();//拿到目標集合陣列
int numNew = a.length;//新增元素的數量
if (numNew == 0)//如果新增元素數量為0,則不增加,並返回false
return false;
Node<E> pred, succ; //index節點的前置節點,後置節點
if (index == size) { //在連結串列尾部追加資料
succ = null; //size節點(隊尾)的後置節點一定是null
pred = last;//前置節點是隊尾
} else {
succ = node(index);//取出index節點,作為後置節點
pred = succ.prev; //前置節點是,index節點的前一個節點
}
//連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的
for (Object o : a) {//遍歷要新增的節點。
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);//以前置節點 和 元素值e,構建new一個新節點,
if (pred == null) //如果前置節點是空,說明是頭結點
first = newNode;
else//否則 前置節點的後置節點設定問新節點
pred.next = newNode;
pred = newNode;//步進,當前的節點為前置節點了,為下次新增節點做準備
}
if (succ == null) {//迴圈結束後,判斷,如果後置節點是null。 說明此時是在隊尾append的。
last = pred; //則設定尾節點
} else {
pred.next = succ; // 否則是在隊中插入的節點 ,更新前置節點 後置節點
succ.prev = pred; //更新後置節點的前置節點
}
size += numNew; // 修改數量size
modCount++; //修改modCount
return true;
}
//根據index 查詢出Node,
Node<E> node(int index) {
// assert isElementIndex(index);
//通過下標獲取某個node 的時候,(增、查 ),會根據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;
}
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size; //插入時的檢查,下標可以是size [0,size]
}
複製程式碼
在構造方法中呼叫addAll方法,相當於是向一個空連結串列中新增集合c中的元素。 如果是在已有元素的連結串列中呼叫addAll方法來新增元素的話,就需要判斷指定的新增位置index是否越界,如果越界會丟擲異常;如果沒有越界,根據新增的位置index,斷開連結串列中index位置的節點前後的引用,加入新元素,重新連上斷開位置的前後節點的引用。過程如下圖:(此處用了xiaoyanger的圖)
(5)toArray()方法:
public Object[] toArray() {
//new 一個新陣列 然後遍歷連結串列,將每個元素存在陣列裡,返回
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
複製程式碼
這個方法的實現很簡單。
(6)總結:
LinkedList 是雙向列表。
連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的。增加一定會修改modCount。 通過下標獲取某個node 的時候,(add select),會根據index處於前半段還是後半段 進行一個折半,以提升查詢效率 刪也一定會修改modCount。 按下標刪,也是先根據index找到Node,然後去連結串列上unlink掉這個Node。 按元素刪,會先去遍歷連結串列尋找是否有該Node,如果有,去連結串列上unlink掉這個Node。 改也是先根據index找到Node,然後替換值。改不修改modCount。 查本身就是根據index找到Node。 所以它的CRUD(Create,Retrieve,Update,Delete)操作裡,都涉及到根據index去找到Node的操作。