Java——LinkedList底層源碼分析


1.簡介

LinkedList 是用鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,他還提供了 List 接口中沒有定義的方法,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用
LinkedList是實現了List接口Deque接口雙端鏈表。 LinkedList底層的鏈表結構使它支持高效的插入和刪除操作,另外它實現了Deque接口,使得LinkedList類也具有隊列的特性。

LinkedList不是線程安全的,如果想使LinkedList變成線程安全的,可以調用靜態類Collections類中的synchronizedList方法:

List list=Collections.synchronizedList(new LinkedList(...));

2.內部結構

3.構造方法

1)空構造方法

public LinkedList() {
}

2)已有的集合創建鏈表構造方法

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

4.添加元素方法

1)add(E e)方法:將元素添加到鏈表尾部

public boolean add(E e) {
    linkLast(e);//這里就只調用了這一個方法
    return true;
}

/**
 * 鏈接使e作為最后一個元素。
 */
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++;
}

2)add(int index, E e):在指定位置添加元素

public void add(int index, E element) {
    checkPositionIndex(index); //檢查索引是否處於[0-size]之間

    if (index == size)//添加在鏈表尾部
        linkLast(element);
    else//添加在鏈表中間
        linkBefore(element, node(index));
}

linkBefore方法需要給定兩個參數,一個插入節點的值,一個指定的節點,所以我們又調用了node(索引)去找到索引對應的節點

3)addAll(Collection c):將集合插入到鏈表尾部

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

4)addAll(int index, Collection c):將集合從指定位置開始插入

public boolean addAll(int index, Collection<? extends E> c) {
    //1:檢查index范圍是否在size之內
    checkPositionIndex(index);

    //2:toArray()方法把集合的數據存到對象數組中
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    //3:得到插入位置的前驅節點和后繼節點
    Node<E> pred, succ;
    //如果插入位置為尾部,前驅節點為last,后繼節點為null
    if (index == size) {
        succ = null;
        pred = last;
    }
    //否則,調用node()方法得到后繼節點,再得到前驅節點
    else {
        succ = node(index);
        pred = succ.prev;
    }

    // 4:遍歷數據將數據插入
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        //創建新節點
        Node<E> newNode = new Node<>(pred, e, null);
        //如果插入位置在鏈表頭部
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    //如果插入位置在尾部,重置last節點
    if (succ == null) {
        last = pred;
    }
    //否則,將插入的鏈表與先前鏈表連接起來
    else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}    

上面可以看出中的addAll方法通常包括下面四個步驟:

  1. 檢查index范圍是否在size之內
  2. toArray()方法把集合的數據存到對象數組中
  3. 得到插入位置的前驅和后繼節點
  4. 遍歷數據,將數據插入到指定位置

5)addFirst(E e):將元素添加到鏈表頭部

public void addFirst(E e) {
    linkFirst(e);
}
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);//新建節點,以頭節點為后繼節點
    first = newNode;
    //如果鏈表為空,last節點也指向該節點
    if (f == null)
        last = newNode;
    //否則,將頭節點的前驅指針指向新節點,也就是指向前一個元素
    else
        f.prev = newNode;
    size++;
    modCount++;
}

6)addLast(E e):將元素添加到鏈表尾部,與add(E e)方法一樣

public void addLast(E e) {
    linkLast(e);
}

5.根據位置取數據方法

1)get(int index): 根據指定索引返回數據

public E get(int index) {
    //檢查index范圍是否在size之內
    checkElementIndex(index);
    //調用Node(index)去找到index對應的node然后返回它的值
    return node(index).item;
}

2)獲取頭節點(index= 0)數據方法:

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}
public E element() {
    return getFirst();
}
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
 }

區別: 

peek(),peekFirst() 對鏈表為空時返回null,

getFirst() 和element() 方法將會在鏈表為空時,拋出異常

element()方法的內部就是使用getFirst()實現的。它們會在鏈表為空時,拋出NoSuchElementException

3)獲取尾節點(index= -1)數據方法:

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

兩者區別: 

getLast()方法在鏈表為空時,會拋出NoSuchElementException

peekLast()只是會返回null。

6.根據對象得到索引的方法

1)int indexOf(Object o):從頭遍歷找

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;
}

2)int lastIndexOf(Object 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;
}

7.檢查鏈表是否包含某對象

1)contains(Object o):檢查對象o是否存在於鏈表中

public boolean contains(Object o) {
    return indexOf(o) != -1;
}

8.刪除元素方法

1)刪除頭節點:remove()removeFirst(),pop()

public E pop() {
    return removeFirst();
}
public E remove() {
    return removeFirst();
}
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

2)removeLast(),pollLast():刪除尾節點

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

區別: 

removeLast()在鏈表為空時將拋出NoSuchElementException

pollLast()方法返回null。

3)remove(Object o):刪除指定元素

public boolean remove(Object o) {
    //如果刪除對象為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;
}

當刪除指定對象時,只需調用remove(Object o)即可,不過該方法一次只會刪除一個匹配的對象,如果刪除了匹配對象,返回true,否則false。

4)unlink(Node 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) {
        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;
}

5)remove(int index):刪除指定位置的元素

public E remove(int index) {
    //檢查index范圍
    checkElementIndex(index);
    //將節點刪除
    return unlink(node(index));
}

參考:https://snailclimb.top/JavaGuide/#/java/collection/LinkedList


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM