1. 程式人生 > >java集合類之LinkedList詳解

java集合類之LinkedList詳解

list詳解 兩種 由於 list接口 add 不為 sel 結點 ESS

一、LinkedList簡介

由於LinkedList是一個實現了Deque的雙端隊列,所以LinkedList既可以當做Queue,又可以當做Stack,在將LinkedList當做Stack時,使用pop()、push()、peek()方法需要註意的是LinkedList內部是將鏈表頭部當做棧頂,鏈表尾部當做棧底

LinkedList是一個雙向鏈表,沒有初始化大小,也沒有擴容機制,就是一直在前面或者後面新增就好

特點:隨機訪問慢、插入刪除速度快

二、源碼分析

由於LinkedList實現了List和Deque兩個接口,所以LinkedList方法分兩種,一種是List接口的方法,第二種是Deque接口的方法

1、全局變量

(1)當前鏈表中的數據個數

 transient int size = 0;

(2)指向鏈表頭部

 transient Node<E> first;

(3)指向鏈表尾部

 transient Node<E> last;

2、構造函數

(1)不帶參數的構造方法

 public LinkedList() {
}

(2)帶collection參數的構造方法

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

說明:當使用第二個構造方法時,會調用addAll()方法將集合中的元素添加到鏈表中

3、方法

(1)LinkLast(E 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++;
}

說明:LinkLast方法就是一個鏈表尾部添加一個雙端節點的操作,但是需要註意對鏈表為空時頭節點的處理
(2) node(ine index)(根據索引index返回node節點)

 Node<E> node(int index) {
    // assert isElementIndex(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;
    }}

(3)linkBefore(E e, Node< E > succ)

 void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    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++;}

(4)linkFirst(E 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++;}

說明

  1. 創建newNode節點,將newNode的後繼指針指向succ,前驅指針指向pred
  2. 將succ的前驅指針指向newNode
  3. 根據pred是否為null,進行不同操作。
    • 如果pred為null,說明該節點插入在頭節點之前,要重置first頭節點
    • 如果pred不為null,那麽直接將pred的後繼指針指向newNode即可

(5)unlink(Node< E > 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;
     }

(6)unlinkFirst(Node f)

 private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
    }

(7)unlinkLast(Node< E > l)

   private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

(1)List接口的添加操作

add(E e)用於將元素添加到鏈表尾部

 public boolean add(E e) {
    linkLast(e);
    return true;
    }

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

 public void add(int index, E element) {
    checkPositionIndex(index);
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

說明:1、檢查index的範圍,否則拋出異常 2、如果插入位置是鏈表尾部,那麽調用LindLast方法 3、如果插入位置是鏈表中間,那麽調用linkBefore方法

addAll有兩個方法,一個參數的方法表示將集合元素添加到鏈表尾部;而兩個參數的方法指定了開始插入的位置。

 //將集合插入到鏈表尾部,即開始索引位置為size
    public boolean addAll(Collection<? extends E> c)     {
        return addAll(size, c);
    }
    
    //將集合從指定位置開始插入
    public boolean addAll(int index, Collection< ? extends E> c) {
    //Step 1:檢查index範圍
    checkPositionIndex(index);
    //Step 2:得到集合的數據
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;
    //Step 3:得到插入位置的前驅節點和後繼節點
    Node<E> pred, succ;
    //如果插入位置為尾部,前驅節點為last,後繼節點為null
    if (index == size) {
        succ = null;
        pred = last;
    }
    //否則,調用node()方法得到後繼節點,再得到前驅節點
    else {
        succ = node(index);
        pred = succ.prev;
    }
    //Step 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;}

說明:1. 檢查index索引範圍
2. 得到集合數據
3. 得到插入位置的前驅和後繼節點
4. 遍歷數據,將數據插入到指定位置

(2)Deque接口的添加操作

push(E e)

    public void push(E e) {
    addFirst(e);
}

說明:push(E e)方法用於將元素添加到鏈表頭部
addFirst(E e)

 public void addFirst(E e) {
    linkFirst(e);}

說明:addFirst(E e)方法用於將元素添加到鏈表頭部

addLast(E e)

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

說明:addLast()方法用於將元素添加到鏈表尾部,與add方法實現一樣,只不過return有區別

offer(E e)

 public boolean offer(E e) {
    return add(e);}

說明:此方法用於將數據添加到鏈表尾部,其內部調用了add(E e)方法

offerFirst(E e)

 public boolean offerFirst(E e) {
    addFirst(e);
    return true;}

說明:此方法用於將數據插入鏈表頭部,與addFirst區別在於該方法返回特定的返回值,而addFirst返回值為void

offerLast(E e)

 public boolean offerLast(E e) {
    addLast(e);
    return true;}

說明:offerLast()與addLast()的區別和offerFirst()和addFirst()的區別一樣

(3)根據位置取數據

獲取任意位置的get(int index)方法

 public E get(int index) {
    //檢查邊界
    checkElementIndex(index);
    return node(index).item;}

說明:get(int index)方法根據指定索引返回數據,如果索引越界,那麽會拋出異常

獲取位置為0的頭節點數據

LinkedList中有多種方法可以獲得頭節點的數據,實現大同小異,區別在於對鏈表為空時的處理,是拋出異常還是返回null

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

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

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

獲取位置為size-1的尾節點數據

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

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

peekLast()方法在鏈表為空時,返回null

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

(4)根據對象獲得索引(一旦匹配,立即返回索引)

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

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

(5)檢查鏈表是否包含對象

contains(Object o)

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

說明:contains(Object o)方法檢查對象o是否存在於鏈表中,此方法調用了IndexOf方法,只要返回結果不是-1,那就說明該對象存在於鏈表中

(6)刪除指定對象

remove(Object o)

 public boolean remove(Object o) {
    //如果刪除對象為null
    if (o == null) {
        //從前向後遍歷
        for (Node<E> x = first; x != null; x = x.next) {
            //一旦匹配,調用unlink()方法和返回true
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        //從前向後遍歷
        for (Node<E> x = first; x != null; x = x.next) {
            //一旦匹配,調用unlink()方法和返回true
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
    }

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

(7)按照位置刪除對象

刪除任意指定位置的對象

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

刪除頭節點的對象
remove()、removeFirst()、pop()在鏈表為空時將拋出NoSuchElementException

 public E remove() {
    return removeFirst();
}

public E pop() {
    return removeFirst();
}

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

poll()、pollFirst()在鏈表為空時返回null

 public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

(8)刪除尾節點的元素

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

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

pollLast()方法在鏈表為空的時候,返回null

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

(9)叠代器操作

iterator()、listIterator()、listIterator(int index)

 public Iterator<E> iterator() {
    return listIterator();
}
    
public ListIterator<E> listIterator() {
    return listIterator(0);
}
   
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

4、內部類

(1)Node< E >

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

說明:從Node的定義可以看出鏈表是一個雙端鏈表的結構

(2)ListItr

   private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;//保存當前modCount,確保fail-fast機制
    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);//得到當前索引指向的next節點
        nextIndex = index;
    }
    public boolean hasNext() {
        return nextIndex < size;
    }
    //獲取下一個節點
    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
    public boolean hasPrevious() {
        return nextIndex > 0;
    }
    //獲取前一個節點,將next節點向前移
    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();
        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }
    public int nextIndex() {
        return nextIndex;
    }
    public int previousIndex() {
        return nextIndex - 1;
    }
    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();
        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }
    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }
    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

三、Deque接口說明

Deque是Queue的子接口,我們知道Queue是一種隊列形式,而Deque則是雙向隊列,它支持從兩個端點方向檢索和插入、刪除元素

方法

當Deque當做隊列使用時(先進先出FIFO),添加元素是添加到隊尾,刪除時刪除的是頭部元素,其中的方法有:

隊列方法 Deque方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

Deque也能當棧用(後進先出)。這時入棧、出棧元素都是在雙端隊列的頭部進行,其中的方法有:

棧方法 Deque方法
push(e) addFirst(e)
oop() removeFirst()
peek() peekFirst()

方法詳解:
void addFirst(E e);

將對象e插入到雙端隊列頭部,容間不足時,拋出IllegalStateException異常;

void addLast(E e);

將對象e插入到雙端隊列尾部,容間不足時,拋出IllegalStateException異常;

boolean offerFirst(E e);

將對象e插入到雙端隊列頭部

boolean offerLast(E e);

將對象e插入到雙端隊列尾部;

E removeFirst();

獲取並移除隊列第一個元素,隊列為空,拋出NoSuchElementException異常;

E removeLast();

獲取並移除隊列最後一個元素,隊列為空,拋出NoSuchElementException異常;

E pollFirst();

獲取並移除隊列第一個元素,隊列為空,返回null;

E pollLast();

獲取並移除隊列最後一個元素,隊列為空,返回null;

E getFirst();

獲取隊列第一個元素,但不移除,隊列為空,拋出NoSuchElementException異常;

E getLast();

獲取隊列最後一個元素,但不移除,隊列為空,拋出NoSuchElementException異常;

E peekFirst();

獲取隊列第一個元素,隊列為空,返回null;

E peekLast();

獲取隊列最後一個元素,隊列為空,返回null;

boolean removeFirstOccurrence(Object o);

移除第一個滿足 (o==null ? e==null : o.equals(e)) 的元素

boolean removeLastOccurrence(Object o);

移除最後一個滿足 (o==null ? e==null : o.equals(e)) 的元素

void push(E e);

將對象e插入到雙端隊列頭部;

E pop();

移除並返回雙端隊列的第一個元素

Iterator descendingIterator();

雙端隊列尾部到頭部的一個叠代器;

實現場景

Deque的實現類主要分為兩種場景

一般場景:

  • LinkedList大小可變的鏈表雙端隊列,允許元素為null
  • ArrayDeque大小可變的數組雙端隊列,不允許null

並發場景

  • LinkedBlockingDeque 如果隊列為空時,獲取操作將會阻塞,知道有元素添加

java集合類之LinkedList詳解