《戰地2042》各版本區別與預購獎勵介紹
阿新 • • 發佈:2021-10-13
概述
ArrayList
和LinkedList
可以說是List
介面的兩種不同的實現。
ArrayList
底層是陣列實現的,所以增刪效率低,但是改查效率高。- 而
LinkedList
底層是連結串列實現的,所以增刪由於不需要移動底層陣列資料,只需要修改連結串列節點指標,所以效率較高。而改查,都需要先定位到目標節點,所以效率較低。
概括的說:LinkedList
是執行緒不安全的,允許元素為null的雙向連結串列。
其底層資料結構是連結串列,它實現List<E>, Deque<E>,Cloneable,java.io.Serializable
介面,
它實現了Deque<E>
ArrayList
相比,沒有實現RandomAccess
介面,所以隨機訪問元素的速度較慢。
RandomAccess
介面
public interface RandomAccess {
}
- 存在的目的:空介面,用來標記這個類是支援快速隨機訪問。
- 在對列表進行訪問的時候,可以有隨機訪問和連續訪問(之後會用程式碼進行演示),自然,這兩種訪問方式是有效率上的區別的,對一個支援隨機訪問的列表應該要使用一個隨機訪問演算法,反而言之,一個適用於連續訪問的列表就應該用連續訪問的演算法。那麼問題來了,你怎麼知道要被遍歷的列表到底是支援那種訪問的?這個介面就是為了區分這個點。
隨機訪問:可以訪問該資料結構中的任意一個節點,比如一個數據有10個元素,你可以直接通過下標來訪問第6個元素。
連續訪問:對於連結串列,如果存在10個節點,要訪問第6個節點,那麼只能從連結串列的頭,依次遍歷相鄰的每一個節點。
因其底層資料結構是連結串列,所以它的增刪只需要移動指標即可,故時間效率較高。不需要批量擴容,也不需要預留空間,所以空間效率比ArrayList
高
缺點是需要隨機訪問元素時,時間效率很低,雖然底層根據下標查詢Node的時候,會根據index判斷目標Node在前半段還是後半段,然後決定是順序還是逆序查詢,以提高時間效率。
欄位
// 集合元素數量 transient int size = 0; // 連結串列頭結點 transient Node<E> first; // 連結串列尾節點 transient Node<E> last;
構造方法
public LinkedList() {
}
// 呼叫 public boolean addAll(Collection<? extends E> c) 將集合c所有元素插入連結串列中
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
節點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;
}
}
可以看出,這是一個雙向連結串列。
增加
1 插入多個節點 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] 閉區間 ----------------------------->checkPositionIndex
Object[] a = c.toArray(); // 拿到目標集合陣列
int numNew = a.length; // 新增元素的數量
if (numNew == 0) // 如果新增元素數量為0,則不增加,並返回false
return false;
Node<E> pred, succ; // pred:index節點的前置節點,succ:index節點的後置節點
if (index == size) { // 在連結串列尾部追加資料
succ = null; // size節點(隊尾)的後置節點一定是null
pred = last; // 前置節點就是隊尾
} else {
succ = node(index); // 取出index節點,作為後置節點 ----------------------------------->node
pred = succ.prev; // 前置節點是,index節點的前一個節點
}
// 連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點的操作。
for (Object o : a) { // 遍歷要新增的節點
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); // 以前置節點 和 元素值e,構建new一個新節點
if (pred == null) // 如果前置節點是null,說明是頭結點
first = newNode; // 更新頭結點first為新節點newNode
else // 否則 前置節點的後置節點設定為新節點
pred.next = newNode;
pred = newNode; // 當前節點為前置節點,為下次新增新節點做準備
}
if (succ == null) { // 迴圈結束後,如果後置節點succ是null,說明此時是在隊尾追加的
last = pred; // 則設定尾節點為pred
} else {// 否則 是在隊中插入的節點,更新前置節點 後置節點
pred.next = succ; // 設定pred的後置節點為succ
succ.prev = pred; // 後置節點succ的前置節點為pred
}
size += numNew; // 修改size
modCount++; // 修改modCount
return true;
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// 根據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;
}
}
小結:
- 連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比
ArrayList
是通過System.arraycopy
完成批量增加的 - 通過下標獲取某個node的時候(新增、查詢),會根據index處於前半段還是後半段進行一個折半,以提升查詢效率、
2 插入單個節點 add
在尾部新增節點
// 在尾部插入一個節點 addpublic boolean add(E e) { linkLast(e); return true;}// 生成新節點 並插入到 連結串列尾部,更新last/first節點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++;//修改size modCount++; // 修改modCount}
在指定下標插入節點
// 在指定下標,index處,插入一個新節點public void add(int index, E element) { checkPositionIndex(index); // 檢查下標是否越界[0,size] if (index == size) // 在尾節點後插入 linkLast(element); else // 在中間插入 linkBefore(element, node(index));}// 在succ節點前,插入一個新節點evoid linkBefore(E e, Node<E> succ) { // assert succ != null; // 儲存後置節點的前置節點 final Node<E> pred = succ.prev; // 以前置節點pred 後置節點succ 和 元素值e 構建一個新節點 final Node<E> newNode = new Node<>(pred, e, succ); // 新節點newNode是原節點succ的前置節點 succ.prev = newNode; if (pred == null) // 如果之前的前置節點是空,說明succ是原頭結點,所以新節點就是現在的頭結點 first = newNode; else // 否則構建前置節點的後置節點為new pred.next = newNode; size++; // 修改數量 modCount++; // 修改modCount}
// 從連結串列尾追加節點public boolean offer(E e) { return add(e);}// 從連結串列頭部插入元素public boolean offerFirst(E e) { addFirst(e); return true;}// 從尾部插入元素public boolean offerLast(E e) { addLast(e); return true;}// 等價於 addFirstpublic void push(E e) { addFirst(e);}
刪除 remove
// 刪:remove目標index的節點public E remove(int index) { checkElementIndex(index); // 檢查是否越界 下標[0,size) 左閉右開 ---------------------->checkElementIndex return unlink(node(index)); // 通過node方法找到index處節點,呼叫unlink方法刪除}// 從連結串列上刪除x節點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) { // 如果前置節點為null,說明x是頭結點 first = next; // 更新頭結點為x的後置節點 } else { prev.next = next; // 將前置節點pre的後置節點設為next節點 x.prev = null; // 將當前節點的前置節點置空 } if (next == null) { // 如果後置節點為空(說明當前節點是原尾節點) last = prev; // 則 尾節點為前置節點 } else { next.prev = prev; // x的後置節點next的前置節點設為pre x.next = null; // 將x的後置節點置空 } x.item = null; // 將當前元素置空,方便GC size--; // 修改數量 modCount++; // 修改modCount return element; // 返回取出的元素值}
private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private boolean isElementIndex(int index) { return index >= 0 && index < size;}
刪除連結串列中的指定節點
// 因為要考慮null元素,需要分情況遍歷public boolean remove(Object o) { // 如果要刪除的是null節點(從remove和add 裡可以看出,允許元素為null) // 遍歷每個節點 對比 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;}
刪除也一定會修改modCount。
按下標刪,也是先根據index找到Node,然後去連結串列上unlink掉這個node。
按元素刪,會先去遍歷連結串列尋找是否有該Node,考慮到允許null,需要分情況遍歷,然後再去unlink這個節點。如果有多個相同元素,會刪除第一個碰到的元素。
修改 set
public E set(int index, E element) { checkElementIndex(index); // 檢查越界[0,size) Node<E> x = node(index); // 取出對應的Node E oldVal = x.item; // 儲存舊item,供返回 x.item = element; // 用新值覆蓋舊值 return oldVal; // 返回舊值}
修改也是先根據index找到Node,然後替換值,不修改modCount。
查詢 get
按下標index查詢節點
public E get(int index) { checkElementIndex(index);// 檢查越界[0,size) return node(index).item; // 呼叫node()方法 取出Node節點}
按元素值從頭到尾查詢下標
// 根據節點物件 查詢下標public int indexOf(Object o) { int index = 0; if (o == null) { // 如果o是null // 遍歷連結串列,找到第一個item是null的節點,返回index 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++; } } // 若沒有找到,返回-1 return -1;}
按元素值從尾到頭查詢下標
// 從尾至頭遍歷連結串列,找到目標元素值為o的節點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;}
查詢不修改modCount
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;}
其他API
// 判斷連結串列是否包含指定元素public boolean contains(Object o) { return indexOf(o) != -1;}// 清空連結串列public void clear() { // 遍歷連結串列, 刪除所有節點, 方便 GC 回收 for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } // 首尾節點值為 null first = last = null; // 元素數量置 0 size = 0; modCount++;}// 克隆 淺拷貝public Object clone() { LinkedList<E> clone = superClone(); // 置為空連結串列 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;}