1. 程式人生 > 遊戲攻略 >《戰地2042》各版本區別與預購獎勵介紹

《戰地2042》各版本區別與預購獎勵介紹

概述

ArrayListLinkedList可以說是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;}