簡單聊聊 LinkedList

哈嘍,大家好,今天我們來簡單聊聊LinkedList

LinkedList是由雙鏈表組成的集合,它不是線程安全的,如果有在多線程中添加或刪除一個或多個元素,需要自己做同步處理,也可以調(diào)用List list = Collections.synchronizedList(new LinkedList(...));來獲取一個線程同步的集合。


下面我們開始簡單分析一下源碼,首先來看看LinkedList這個類實(shí)現(xiàn)了哪些關(guān)鍵的接口。

    public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable

我們可以從源碼中看出LinkedList實(shí)現(xiàn)了List接口來方便集合操作,同時也實(shí)現(xiàn)了Deque接口,這樣就有了許多操作鏈表的方法。

Node

Node類是LinkedList的一個內(nèi)部類,這個類是鏈表中存放元素用的??聪略创a

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

有三個參數(shù),item是當(dāng)前元素的值,next指的是下一個元素,prev指的是上一個元素。

重要參數(shù)

    transient Node<E> first;

    transient Node<E> last;

在LinkedList中有兩個比較重要的參數(shù),一個是first,指的是鏈表中第一個元素。而last指的就是鏈表中最后一個元素。

LinkedList()

    public LinkedList() {
    }

這個LinkedList的一個空構(gòu)造函數(shù),沒有做任何其他操作。

LinkedList(Collection<? extends E> c)

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

這是一個帶參數(shù)的構(gòu)造函數(shù),將傳入進(jìn)來的集合全部添加到了LinkedList中,addAll這個方法我們在后面進(jìn)行講解。

getFirst()

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

這個方法是獲取第一個元素的值,這里是直接將first賦值給f,因?yàn)閒irst在LinkedList中就是指的第一個元素。如果f為null的話就會報錯。最后會返回f的值。

getLast()

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

這個方法是獲取最后一個元素的值,這個方法和getFirst()方法類似,直接將last賦值給l,最后返回l的值。

removeFirst()

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

    private E unlinkFirst(Node<E> f) {
        //將f的值賦值給element。
        final E element = f.item;
        //將f的下一個元素賦值給next。
        final Node<E> next = f.next;
        //將f的值和f中的next都設(shè)置為null,方便GC
        f.item = null;
        f.next = null; // help GC
        //將first設(shè)置為f的next
        first = next;
        //如果next為null,證明鏈表中只有一個元素,那么將最后一個元素也設(shè)置為null。
        if (next == null)
            last = null;
        else
        //如果不為null,那么將next的prev設(shè)置為null,原本指向的是f。
            next.prev = null;
        size--;
        modCount++;
        //最后返回f的值。
        return element;
    }

這個方法是移除第一個元素。還是先將first賦值給f,然后調(diào)用unlinkFirst方法并將f傳入。unlinkFirst的相關(guān)解釋寫在了代碼中。

removeLast()

    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(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;
    }

這個方法和removeFirst方法邏輯差不多,是將最后一個元素置為null,然后將倒數(shù)第二個元素賦值給last。

addFirst(E e)

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

    private void linkFirst(E e) {
        //將first賦值給f。
        final Node<E> f = first;
        //根據(jù)傳入的值e,new一個新的Node出來,并將f傳入,設(shè)置為新Node的下一個元素。
        final Node<E> newNode = new Node<>(null, e, f);
        //將第一個元素設(shè)置為新的Node。
        first = newNode;
        //如果f為null表示原本鏈表中沒有元素,這時候添加了一個元
        //素后第一個,最后一個元素都是newNode,所以要設(shè)置last為newNode。
        if (f == null)
            last = newNode;
        else
        //否則將原本第一個元素的prev設(shè)置為newNode。
            f.prev = newNode;
        size++;
        modCount++;
    }

在鏈表的頭部添加一個元素,關(guān)鍵解釋放在了代碼中。

addLast(E e)

    public void addLast(E e) {
        linkLast(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++;
    }

addLast和addFirst大致邏輯差不多。只是將元素加在了鏈表最后,并重新設(shè)置了last的值。

add(E e)

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

add(E e)方法和addLast(E e)相比只是多了一個返回值。

remove(Object o)

    public boolean remove(Object o) {
        //先判斷傳入的o是否為null。
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                //如果為null,則用==來比較。
                if (x.item == null) {
                    //調(diào)用unlink方法來刪除元素。
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                //如果不為null,則用equals來比較值。
                if (o.equals(x.item)) {
                    //調(diào)用unlink方法來刪除元素。
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

    E unlink(Node<E> x) {
        // assert x != null;
        //將要刪除元素x的item,next,prev分別提出來。
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            //如果x的prev為null,證明x為第一個元素,刪除x后,這里就將next設(shè)置為第一個元素。
            first = next;
        } else {
            //如果不為空,將x元素上一個元素的next指向x的next。
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            //如果x的next為null,說明x為最后一個元素,設(shè)置last為x的上一個元素。
            last = prev;
        } else {
            //如果不為空,將x的下一個元素的prev指向x的prev。
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

remove(Object o)方法的關(guān)鍵解釋已經(jīng)放在了代碼中。

addAll(Collection<? extends E> c)

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

    public boolean addAll(int index, Collection<? extends E> c) {
        //先檢查傳入的index是否越界,因?yàn)檫@個的index默認(rèn)為size,所以會將c直接添加到鏈表末尾。
        checkPositionIndex(index);

        //將c轉(zhuǎn)為數(shù)組,并判斷c的長度,如果為0,說明c為空數(shù)組,直接返回false。
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
             //如果index等于size,說明將添加到鏈表末尾,所以pred為last。
            succ = null;
            pred = last;
        } else {
            //如果index不等于size,說明將添加到鏈表中,將目前index的元素賦值給succ,
            //并將上一個元素賦值給pred。
            succ = node(index);
            pred = succ.prev;
        }

        //通過for循環(huán)生成新的Node實(shí)例,然后將每一個新的Node以此添加到pred后面。
        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;
        }

         //添加完成后如果succ為null,則為在鏈表末尾添加,我們將我們添加的最后一個元素設(shè)置為last。
        //如果succ不為null,則將原本index的下一個元素添加到prev后面。
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

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

addAll(Collection<? extends E> c)方法的關(guān)鍵解釋放在了代碼中。

clear()

    public void clear() {
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }

clear()方法很簡單,通過for循環(huán)依次將元素設(shè)置為null。

get(int index)

    public E get(int index) {
        //檢查index是否越界。
        checkElementIndex(index);
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    Node<E> node(int 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;
        }
    }

在get(int index)方法中,會先判斷index是在鏈表的前一半還是在后一半,然后分別從第一個元素或者是最后一個元素來看是向前或向后查找index對應(yīng)的元素。這樣作會使查找速度更快。

set(int index, E element)

    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

這里直接調(diào)用node方法獲取index對應(yīng)的元素,然后將元素的值替換為element。

add(int index, E element)

    public void add(int index, E element) {
        checkPositionIndex(index);
        
        if (index == size)
            //如果index為size則將element放入新的Node中并添加到鏈表末尾。
            linkLast(element);
        else
            //否則將element放入新的Node中,添加到index對應(yīng)元素前面。
            linkBefore(element, node(index));
    }

    void linkBefore(E e, Node<E> succ) {
        //獲取index對應(yīng)元素的前一個元素。
        final Node<E> pred = succ.prev;
        //新的Node位置在index對應(yīng)元素和前一個元素之間。
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

add(int index, E element)方法的解釋已經(jīng)放在了代碼中。

remove(int index)

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

remove(int index)方法先檢查index是否越界,然后調(diào)用node方法獲取index對應(yīng)的元素,最后調(diào)用unlink方法刪除這個元素。

indexOf

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

先判斷傳入的o是否為null,如果為null則在for循環(huán)中用==來比較,如果不為null,則用equals來比較值,最后返回元素對應(yīng)的index,如果沒有對應(yīng)的元素,則返回-1。

toArray()

    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

toArray()方法先生成一個相同size的數(shù)組,然后通過for循環(huán)將鏈表的值全部賦值給數(shù)組,最后返回數(shù)組。


到這里L(fēng)inkedList的一些基本方法就分析完成了,代碼并不是很復(fù)雜,我們通過分析源碼可以發(fā)現(xiàn)LinkedList在增加,刪除方面代碼很簡單,相對應(yīng)的速度也就比較快。在查找,修改方面的代碼就比較復(fù)雜,每次都會從頭開始去找相應(yīng)的元素,速度也就會比較慢。綜合來看如果你的需求中有大量的增加,刪除的話可以考慮使用LinkedList。

如果文中有錯誤的地方歡迎大家指出來,也可以留言交流。

3Q

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容