1. 程式人生 > >重拾Java之LinkedList原始碼閱讀

重拾Java之LinkedList原始碼閱讀

     上文我們檢視ArrayList的原始碼(重拾Java之ArrayList原始碼閱讀),接著我們來瞅瞅LinkedList有什麼神奇之處。ArrayList的資料儲存方式是陣列,LinkedList裡面儲存資料的方式是連結串列,什麼是連結串列了?你可以將其理解為一列火車,每一節車廂就是一個節點(Node)。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;
        }
    }

item是儲存的資料物件;prev是該節點指向的上一個節點;next是該節點指向的下一個節點。接著我們看看LinkedList的中的幾個非常重要的全域性變數:

//LinkedList中元素的個數
transient int size = 0;
//LinkedList中連結串列的頭節點
transient Node<E> first;
//LinkedList中連結串列的尾節點
transient Node<E> last;

 一、LinkedList的構造方法

無引數的就一個空方法,沒啥好看的,我們看看其有參建構函式:

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

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

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

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

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

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

addAll這個方法寫的很出彩,考慮的非常的全面;這個方法是一個public方法,開發者可以直接外部呼叫的。首先該方法對新增的索引進行了異常的判斷,接著對新增的集合進行了異常的判斷。接著程式碼18行到25行考慮的很全面:當index==size 成立的時候表示新增的集合資料是從原來連結串列的連結串列的尾部開始新增的;不成立的時候表示是從連結串列的中間進行新增的。緊接著27到35行是向連結串列中新增資料。第30行成立的時候表示時原來的連結串列是空連結串列。37行到42行程式碼;當37行程式碼成立的時候表示的是向空連結串列中新增資料的,比如我們用集合初始化LinkedList。modCount和在ArrayList中一樣,同樣記錄的是集合大小改變的次數。

二、LinkedList的增刪改查以及遍歷方法

    1.增加資料 add方法

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

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

可見這個方法非常的單純的,首先建立了一個新的節點,然後判斷last指向節點是不是為空。如果為空的話,那表示之前的連結串列是空的。

2.刪除資料 remove方法

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

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

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

remove方法首先檢查要移除的節點的索引是否合法;接著使用node方法進行該索引位置節點的查詢;node方法中 size>>1表示size/2;如果索引小於該值就從first指向的節點找,大於的話就從last指向的節點查詢(有點二分法查詢的意思)。然後使用unlink方法進行刪除節點(其中該方法需要考慮,要刪除的節點是頭節點或者尾節點)。

3.修改資料  set方法

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

同樣是先檢查該索引是否合法;然後使用node方法找到該索引對應的節點。然後直接操作該節點即可。

4.查詢資料 get方法

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

同樣get方法,沒什麼亮點,非常的善良。

5.遍歷資料 迭代器方法:

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

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            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;
        }

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

比較簡單,沒啥亮點.

LinkedList實現了Deque介面,該介面是繼承Queue介面的;所以LinkedList可以充當佇列Queue的實現類。

public interface Queue<E> extends Collection<E> {
    //新增元素
    boolean add(E e);

    //新增元素
    boolean offer(E e);

    //返回移除的元素,並將該元素移除
    E remove();

    //返回佇列頭部元素並將其刪除
    E poll();

    //返回佇列頭部元素,不刪除佇列的頭部元素
    E element();

    //返回佇列頭部元素,不刪除佇列的頭部元素
    E peek();
}

offer方法沒啥亮點,其實就是呼叫add方法。我們看看poll、element、peek這三個方法,首先可以排除poll方法;它返回對頭但是會把隊頭刪掉。而element和peek方法不會。下面來看看element和peek方法的原始碼:

public E element() {
        return getFirst();
    }

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

可以看出這兩個方法並沒有太大的區別。僅僅只是element方法如果佇列的頭部是空的,即如果是空列表的話,會丟擲異常!peek方法了?如果佇列是空的話,只會返回null而不會丟擲異常。類似的LinkedList中含有很多過載的方法;都差不多就不一一的分析,讀者可以自己看看;當然還有一些JDK 1.8中新增的方法;這個和ArrayList中的類似,讀者可以看我之前的分析ArrayList的原始碼的文章。

三、小結

到目前為止,我們已經分析完ArrayList和LinkedList的原始碼;對比之前的ArrayLsit原始碼,我們不難發現以前的結論:ArrayList做查詢很快,但不是適合做刪除和插入操作;LinkedList做查詢操作比ArrayList慢,但是做插入和刪除操作是優於ArrayLsit的。

備註:有什麼寫的有問題的,歡迎讀者在評論區斧正,謝謝。為了我們面試不被面試官按到地上摩擦,我們還是要多看看原始碼。


相關推薦

JavaLinkedList原始碼閱讀

     上文我們檢視ArrayList的原始碼(重拾Java之ArrayList原始碼閱讀),接著我們來瞅瞅LinkedList有什麼神奇之處。ArrayList的資料儲存方式是陣列,LinkedList裡面儲存資料的方式是連結串列,什麼是連結串列了?你可以將其理解為一列火

JavaLinkedList原始碼解讀(JDK 1.8)

java.util.LinkedList     雙向連結串列實現的List。    基於JDK 1.8。    沒有使用標準的註釋,並適當調整了程式碼的縮排以方便介紹。    裡面很多方法的實現是一樣的,不過可以讓外界感覺其提供了更多的行為。    需要花比Array

Memcache-Java-Client-Release原始碼閱讀七)

一、主要內容 本章節的主要內容是介紹Memcache Client的Native,Old_Compat,New_Compat三個Hash演算法的應用及實現。 二、準備工作 1、伺服器啟動192.168.0.106:11211,192.168.0.106:11212兩個服務端例項。

Memcache-Java-Client-Release原始碼閱讀六)

一、主要內容 本章節的主要內容是介紹Memcache Client的一致性Hash演算法的應用及實現。 二、準備工作 1、伺服器啟動192.168.0.106:11211,192.168.0.106:11212兩個服務端例項。 2、示例程式碼: String[] serve

4.Java集合框架剖析 LinkedList原始碼剖析

1 package java.util; 2 3 import java.util.function.Consumer; 4 5 //LinkedList基於連結串列實現 6 //實現了List、Deque、Cloneable、Serializable介面 7 publi

Java集合LinkedList原始碼分析

概述 LinkedLIst和ArrayLIst一樣, 都實現了List介面, 但其內部的資料結構不同, LinkedList是基於連結串列實現的(從名字也能看出來), 隨機訪問效率要比ArrayList差. 它的插入和刪除操作比ArrayList更加高效, 但還是要遍歷部分連結串列的指標才能移動到下標所指的

Java進階----LinkedList原始碼分析

// 什麼都沒做,是一個空實現 public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }

Java--功底篇HashMap

最近靠了些關於HashMap的相關內容,覺得有必要梳理一下了。 1、HashMap概述 HashMap是常用的一個集合類,它是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵。此類不保證對映的順序,特別是

Java集合類原始碼閱讀AbstractCollection

private static <T> T[] finishToArray(T[] r, Iterator<?> it) { int i = r.length; while (it.hasNext()) { int cap = r.

2基本類型數組和枚舉類型——Java

arraycopy 字符 第一個 system.in abs xtend 劃線 ann else 2.1 標識符和關鍵字 2.1.1標識符 標識符:用來標誌類名、變量名、方法名、類型名、數組名、文件名的有效字符序列稱為標識符。簡單地說,標識符就是一個名字。 Java關於標識

java.nio.Buffer原始碼閱讀

Java 自從 JDK1.4 起,對各種 I/O 操作使用了 Buffer 和 Channel 技術。這種更接近於作業系統的的底層操作使得 I/O 操作速度得到大幅度提升,下面引用一段《Java 程式設計思想》對於 Buffer(緩衝器)和 Channel 的形象化解釋。 我們可以將它想象成一個煤礦,

Java集合——LinkedList原始碼分析

一 前言 上一篇我們介紹了ArrayList原始碼解析有想看的同學可以點選這個連結ArrayList原始碼解析。平時我們或多或少都用過LinKedList,但是對其原理不是很瞭解,我們就來一起學習吧。 二 原始碼解析 1. LinkedList概述 LinkedList是

java 通訊錄原始碼

1️⃣聯絡人類 package com.kll.LinkMan; public class LinkMan { //聯絡人:姓名 年齡 性別 地址 電話 private String name = null; private int age = 0

Java集合 | LinkedList 原始碼分析(JDK 1.8)

一、基本圖示 二、基本介紹 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, De

Java第一波 介面有什麼用

      畢業以後工作了一年,本來不想從事程式設計工作,畢竟這個變禿就能變強的職業有點嚇人。但現在發現還是這個行業才能稍微在現今的物質生活中過得比較舒適,所以開始拿起放下一年的專業---軟體工程。       一開始也在前端,java,c++三個方向猶豫了很久。但是最後還

演算法劍指Offier——棧的壓入、彈出序列

題目描述 輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注

演算法劍指Offier——包含min函式的棧

題目描述 定義棧的資料結構,請在該型別中實現一個能夠得到棧最小元素的min函式。 import java.util.Stack; public class Solution { Stack<Integer> stack1 = new Stack

演算法劍指Offier——從上往下列印二叉樹

題目描述 從上往下打印出二叉樹的每個節點,同層節點從左至右列印。 import java.util.ArrayList; import java.util.Queue; import java.util.LinkedList; class TreeNode {

演算法劍指Offier——二叉樹的映象

劍指Offier——二叉樹的映象 題目描述 操作給定的二叉樹,將其變換為源二叉樹的映象。 輸入描述: 二叉樹的映象定義:源二叉樹 8 / \ 6 10 / \ / \ 5 7 9 11 映象二叉樹 8 / \ 10 6 / \ / \ 11 9 7

演算法劍指Offier——樹的子結構

樹的子結構 題目描述 輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構) class TreeNode { int val = 0; TreeNode left = null; TreeNode ri