Java集合干貨系列-(二)LinkedList源碼解析

前言

今天來(lái)介紹下LinkedList灾搏,在集合框架整體框架一章中拯刁,我們介紹了List接口捂人,LinkedList與ArrayList一樣實(shí)現(xiàn)List接口鸟款,只是ArrayList是List接口的大小可變數(shù)組的實(shí)現(xiàn)蓝谨,LinkedList是List接口鏈表的實(shí)現(xiàn)灌具∏嗤牛基于鏈表實(shí)現(xiàn)的方式使得LinkedList在插入和刪除時(shí)更優(yōu)于ArrayList,而隨機(jī)訪問(wèn)則比ArrayList遜色些咖楣。
構(gòu)造圖如下:
藍(lán)色線條:繼承
綠色線條:接口實(shí)現(xiàn)


正文

LinkedList是基于鏈表結(jié)構(gòu)的一種List督笆,在分析LinkedList源碼前有必要對(duì)鏈表結(jié)構(gòu)進(jìn)行說(shuō)明。

1.鏈表的概念
鏈表是由一系列非連續(xù)的節(jié)點(diǎn)組成的存儲(chǔ)結(jié)構(gòu)诱贿,簡(jiǎn)單分下類的話娃肿,鏈表又分為單向鏈表和雙向鏈表,而單向/雙向鏈表又可以分為循環(huán)鏈表和非循環(huán)鏈表珠十,下面簡(jiǎn)單就這四種鏈表進(jìn)行圖解說(shuō)明料扰。

1.1.單向鏈表
單向鏈表就是通過(guò)每個(gè)結(jié)點(diǎn)的指針指向下一個(gè)結(jié)點(diǎn)從而鏈接起來(lái)的結(jié)構(gòu),最后一個(gè)節(jié)點(diǎn)的next指向null焙蹭。

1.2.單向循環(huán)鏈表
單向循環(huán)鏈表和單向列表的不同是晒杈,最后一個(gè)節(jié)點(diǎn)的next不是指向null,而是指向head節(jié)點(diǎn)孔厉,形成一個(gè)“環(huán)”拯钻。

1.3.雙向鏈表
從名字就可以看出,雙向鏈表是包含兩個(gè)指針的撰豺,pre指向前一個(gè)節(jié)點(diǎn)粪般,next指向后一個(gè)節(jié)點(diǎn),但是第一個(gè)節(jié)點(diǎn)head的pre指向null郑趁,最后一個(gè)節(jié)點(diǎn)的tail指向null刊驴。

1.4.雙向循環(huán)鏈表
雙向循環(huán)鏈表和雙向鏈表的不同在于姿搜,第一個(gè)節(jié)點(diǎn)的pre指向最后一個(gè)節(jié)點(diǎn)寡润,最后一個(gè)節(jié)點(diǎn)的next指向第一個(gè)節(jié)點(diǎn),也形成一個(gè)“環(huán)”舅柜。而LinkedList就是基于雙向循環(huán)鏈表設(shè)計(jì)的梭纹。

更形象的解釋下就是:雙向循環(huán)鏈表就像一群小孩手牽手圍成一個(gè)圈,第一個(gè)小孩的右手拉著第二個(gè)小孩的左手致份,第二個(gè)小孩的左手拉著第一個(gè)小孩的右手变抽。。氮块。最后一個(gè)小孩的右手拉著第一個(gè)小孩的左手绍载。

ok,鏈表的概念介紹完了滔蝉,下面進(jìn)入寫注釋和源碼分析部分击儡,但是在這之前還是要提醒一句,不是啰嗦哦蝠引,鏈表操作理解起來(lái)比數(shù)組困難了不少阳谍,所以務(wù)必要理解上面的圖解蛀柴,如果源碼解析過(guò)程中遇到理解困難,請(qǐng)返回來(lái)照?qǐng)D理解矫夯。

LinkedList簡(jiǎn)介

LinkedList定義

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

LinkedList 是一個(gè)繼承于AbstractSequentialList的雙向循環(huán)鏈表鸽疾。它也可以被當(dāng)作堆棧、隊(duì)列或雙端隊(duì)列進(jìn)行操作训貌。
LinkedList 實(shí)現(xiàn) List 接口制肮,能對(duì)它進(jìn)行隊(duì)列操作。
LinkedList 實(shí)現(xiàn) Deque 接口旺订,即能將LinkedList當(dāng)作雙端隊(duì)列使用弄企。
LinkedList 實(shí)現(xiàn)了Cloneable接口,即覆蓋了函數(shù)clone()区拳,能克隆拘领。
LinkedList 實(shí)現(xiàn)java.io.Serializable接口,這意味著LinkedList支持序列化樱调,能通過(guò)序列化去傳輸约素。
LinkedList 是非同步的。

LinkedList屬性

明白了上面的鏈表概念笆凌,以及LinkedList是基于雙向循環(huán)鏈表設(shè)計(jì)的圣猎,下面在具體來(lái)看看LinkedList的底層的屬性

private transient Entry<E> header = new Entry<E>(null, null, null);
2 private transient int size = 0;

LinkedList中提供了上面兩個(gè)屬性,其中size和ArrayList中一樣用來(lái)計(jì)數(shù)乞而,表示list的元素?cái)?shù)量送悔,而header則是鏈表的頭結(jié)點(diǎn),Entry則是鏈表的節(jié)點(diǎn)對(duì)象爪模。

private static class Entry<E> {
    E element;  // 當(dāng)前存儲(chǔ)元素
    Entry<E> next;  // 下一個(gè)元素節(jié)點(diǎn)
    Entry<E> previous;  // 上一個(gè)元素節(jié)點(diǎn)
    Entry(E element, Entry<E> next, Entry<E> previous) {
        this.element = element;
        this.next = next;
        this.previous = previous;
    }
}

Entry為L(zhǎng)inkedList 的內(nèi)部類欠啤,其中定義了當(dāng)前存儲(chǔ)的元素,以及該元素的上一個(gè)元素和下一個(gè)元素屋灌。結(jié)合上面雙向鏈表的示意圖很容易看懂洁段。

LinkedList構(gòu)造函數(shù)

/**
* 構(gòu)造一個(gè)空的LinkedList .
*/
public LinkedList() {
    //將header節(jié)點(diǎn)的前一節(jié)點(diǎn)和后一節(jié)點(diǎn)都設(shè)置為自身
    header.next = header. previous = header ;
}

/**
* 構(gòu)造一個(gè)包含指定 collection 中的元素的列表,這些元素按其 collection 的迭代器返回的順序排列
*/
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

需要注意的是空的LinkedList構(gòu)造方法共郭,它將header節(jié)點(diǎn)的前一節(jié)點(diǎn)和后一節(jié)點(diǎn)都設(shè)置為自身祠丝,這里便說(shuō)明LinkedList 是一個(gè)雙向循環(huán)鏈表,如果只是單存的雙向鏈表而不是循環(huán)鏈表除嘹,他的實(shí)現(xiàn)應(yīng)該是這樣的:

public LinkedList() {
    header.next = null;
    header. previous = null;
}

非循環(huán)鏈表的情況應(yīng)該是header節(jié)點(diǎn)的前一節(jié)點(diǎn)和后一節(jié)點(diǎn)均為null(參見(jiàn)鏈表圖解)写半。

API方法摘要


LinkedList源碼解析(基于JDK1.6.0_45)

增加

增加方法的代碼讀起來(lái)比較不容易理解,需要的時(shí)候請(qǐng)結(jié)合鏈表圖解尉咕。

/**
 * 將一個(gè)元素添加至list尾部
 */
public boolean add(E e) {
   // 在header前添加元素e叠蝇,header前就是最后一個(gè)結(jié)點(diǎn)啦,就是在最后一個(gè)結(jié)點(diǎn)的后面添加元素e
   addBefore(e, header);
    return true;
}
/**
 * 在指定位置添加元素
 */
public void add(int index, E element) {
    // 如果index等于list元素個(gè)數(shù)龙考,則在隊(duì)尾添加元素(header之前)蟆肆,否則在index節(jié)點(diǎn)前添加元素
    addBefore(element, (index== size ? header : entry(index)));
}
 
private Entry<E> addBefore(E e, Entry<E> entry) {
    // 用entry創(chuàng)建一個(gè)要添加的新節(jié)點(diǎn)矾睦,next為entry,previous為entry.previous炎功,意思就是新節(jié)點(diǎn)插入entry前面枚冗,確定自身的前后引用,
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
     // 下面修改newEntry的前后節(jié)點(diǎn)的引用蛇损,確保其鏈表的引用關(guān)系是正確的
    // 將上一個(gè)節(jié)點(diǎn)的next指向自己
    newEntry. previous.next = newEntry;
    // 將下一個(gè)節(jié)點(diǎn)的previous指向自己
    newEntry. next.previous = newEntry;
    // 計(jì)數(shù)+1
     size++;
     modCount++;
     return newEntry;
}

到這里可以發(fā)現(xiàn)一點(diǎn)疑慮赁温,header作為雙向循環(huán)鏈表的頭結(jié)點(diǎn)是不保存數(shù)據(jù)的,也就是說(shuō)hedaer中的element永遠(yuǎn)等于null淤齐。

/**
 * 添加一個(gè)集合元素到list中
 */
public boolean addAll(Collection<? extends E> c) {
        // 將集合元素添加到list最后的尾部
    return addAll(size , c);
}

/**
 * 在指定位置添加一個(gè)集合元素到list中
 */
public boolean addAll(int index, Collection<? extends E> c) {
    // 越界檢查
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException( "Index: "+index+
                                            ", Size: "+size );
    Object[] a = c.toArray();
    // 要插入元素的個(gè)數(shù)
    int numNew = a.length ;
    if (numNew==0)
        return false;
    modCount++;

    // 找出要插入元素的前后節(jié)點(diǎn)
    // 獲取要插入index位置的下一個(gè)節(jié)點(diǎn)股囊,如果index正好是lsit尾部的位置那么下一個(gè)節(jié)點(diǎn)就是header,否則需要查找index位置的節(jié)點(diǎn)
    Entry<E> successor = (index== size ? header : entry(index));
    // 獲取要插入index位置的上一個(gè)節(jié)點(diǎn)更啄,因?yàn)槭遣迦胫烧睿陨弦粋€(gè)點(diǎn)擊就是未插入前下一個(gè)節(jié)點(diǎn)的上一個(gè)
    Entry<E> predecessor = successor. previous;
    // 循環(huán)插入
    for (int i=0; i<numNew; i++) {
        // 構(gòu)造一個(gè)節(jié)點(diǎn),確認(rèn)自身的前后引用
        Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
        // 將插入位置上一個(gè)節(jié)點(diǎn)的下一個(gè)元素引用指向當(dāng)前元素(這里不修改下一個(gè)節(jié)點(diǎn)的上一個(gè)元素引用祭务,是因?yàn)橄乱粋€(gè)節(jié)點(diǎn)隨著循環(huán)一直在變)
        predecessor. next = e;
        // 最后修改插入位置的上一個(gè)節(jié)點(diǎn)為自身内狗,這里主要是為了下次遍歷后續(xù)元素插入在當(dāng)前節(jié)點(diǎn)的后面,確保這些元素本身的順序
        predecessor = e;
    }
    // 遍歷完所有元素义锥,最后修改下一個(gè)節(jié)點(diǎn)的上一個(gè)元素引用為遍歷的最后一個(gè)元素
    successor. previous = predecessor;

    // 修改計(jì)數(shù)器
    size += numNew;
    return true;
}


增加方法的代碼理解起來(lái)可能有些困難柳沙,但是只要理解了雙向鏈表的存儲(chǔ)結(jié)構(gòu),掌握增加的核心邏輯就可以了拌倍,這里總結(jié)一下往鏈表中增加元素的核心邏輯:1.將元素轉(zhuǎn)換為鏈表節(jié)點(diǎn)赂鲤,2.增加該節(jié)點(diǎn)的前后引用(即pre和next分別指向哪一個(gè)節(jié)點(diǎn)),3.前后節(jié)點(diǎn)對(duì)該節(jié)點(diǎn)的引用(前節(jié)點(diǎn)的next指向該節(jié)點(diǎn)柱恤,后節(jié)點(diǎn)的pre指向該節(jié)點(diǎn))∈酰現(xiàn)在再看下就這么簡(jiǎn)單么,就是改變前后的互相指向關(guān)系(看圖增加元素前后的變化)膨更。

其實(shí)刪除也是一樣的對(duì)不對(duì)妙真?下面看看刪除方法的實(shí)現(xiàn)缴允。

刪除

/**
 * 刪除第一個(gè)匹配的指定元素
 */
public boolean remove(Object o) {
     // 遍歷鏈表找到要被刪除的節(jié)點(diǎn)
    if (o==null) {
        for (Entry<E> e = header .next; e != header; e = e.next ) {
            if (e.element ==null) {
                remove(e);
                return true;
            }
        }
    } else {
        for (Entry<E> e = header .next; e != header; e = e.next ) {
            if (o.equals(e.element )) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}

private E remove(Entry<E> e) {
    if (e == header )
       throw new NoSuchElementException();

   // 被刪除的元素荚守,供返回
    E result = e. element;
   // 下面修正前后對(duì)該節(jié)點(diǎn)的引用
   // 將該節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)的next指向該節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
   e. previous.next = e.next;
   // 將該節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的previous指向該節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
   e. next.previous = e.previous;
   // 修正該節(jié)點(diǎn)自身的前后引用
    e. next = e.previous = null;
   // 將自身置空,讓gc可以盡快回收
    e. element = null;
   // 計(jì)數(shù)器減一
    size--;
    modCount++;
    return result;
}

上面對(duì)于鏈表增加元素總結(jié)了练般,一句話就是“改變前后的互相指向關(guān)系”矗漾,刪除也是同樣的道理,由于節(jié)點(diǎn)被刪除薄料,該節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)和下一個(gè)節(jié)點(diǎn)互相拉一下小手就可以了敞贡,注意的是“互相”,不能一廂情愿摄职。

修改

/**
 * 修改指定位置索引位置的元素
 */
public E set( int index, E element) {
    // 查找index位置的節(jié)點(diǎn)
    Entry<E> e = entry(index);
    // 取出該節(jié)點(diǎn)的元素誊役,供返回使用
    E oldVal = e. element;
    // 用新元素替換舊元素
    e. element = element;
    // 返回舊元素
    return oldVal;
}    

set方法看起來(lái)簡(jiǎn)單了很多获列,只要修改該節(jié)點(diǎn)上的元素就好了,但是不要忽略了這里的entry()方法蛔垢,重點(diǎn)就是它击孩。

查詢

終于到查詢了,終于發(fā)現(xiàn)了上面經(jīng)常出現(xiàn)的那個(gè)方法entry()根據(jù)index查詢節(jié)點(diǎn)鹏漆,我們知道數(shù)組是有下標(biāo)的巩梢,通過(guò)下標(biāo)操作天然的支持根據(jù)index查詢?cè)兀湵碇惺菦](méi)有index概念呢艺玲,那么怎么樣才能通過(guò)index查詢到對(duì)應(yīng)的元素呢括蝠,下面就來(lái)看看LinkedList是怎么實(shí)現(xiàn)的。

/**
 * 查找指定索引位置的元素
 */
public E get( int index) {
    return entry(index).element ;
}

/**
 * 返回指定索引位置的節(jié)點(diǎn)
 */
private Entry<E> entry( int index) {
    // 越界檢查
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException( "Index: "+index+
                                            ", Size: "+size );
    // 取出頭結(jié)點(diǎn)
    Entry<E> e = header;
    // size>>1右移一位代表除以2饭聚,這里使用簡(jiǎn)單的二分方法忌警,判斷index與list的中間位置的距離
    if (index < (size >> 1)) {
        // 如果index距離list中間位置較近,則從頭部向后遍歷(next)
        for (int i = 0; i <= index; i++)
            e = e. next;
    } else {
        // 如果index距離list中間位置較遠(yuǎn)秒梳,則從頭部向前遍歷(previous)
        for (int i = size; i > index; i--)
            e = e. previous;
    }
    return e;
}

現(xiàn)在知道了慨蓝,LinkedList是通過(guò)從header開(kāi)始index計(jì)為0,然后一直往下遍歷(next)端幼,直到到底index位置。為了優(yōu)化查詢效率婆跑,LinkedList采用了二分查找(這里說(shuō)的二分只是簡(jiǎn)單的一次二分)滑进,判斷index與size中間位置的距離犀忱,采取從header向后還是向前查找。
到這里我們明白扶关,基于雙向循環(huán)鏈表實(shí)現(xiàn)的LinkedList节槐,通過(guò)索引Index的操作時(shí)低效的搀庶,index所對(duì)應(yīng)的元素越靠近中間所費(fèi)時(shí)間越長(zhǎng)。而向鏈表兩端插入和刪除元素則是非常高效的(如果不是兩端的話铜异,都需要對(duì)鏈表進(jìn)行遍歷查找)。

是否包含

// 判斷LinkedList是否包含元素(o)
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

// 從前向后查找咆蒿,返回“值為對(duì)象(o)的節(jié)點(diǎn)對(duì)應(yīng)的索引”
// 不存在就返回-1
public int indexOf(Object o) {
    int index = 0;
    if (o==null) {
        for (Entry e = header .next; e != header; e = e.next ) {
            if (e.element ==null)
                return index;
            index++;
        }
    } else {
        for (Entry e = header .next; e != header; e = e.next ) {
            if (o.equals(e.element ))
                return index;
            index++;
        }
    }
    return -1;
}

// 從后向前查找,返回“值為對(duì)象(o)的節(jié)點(diǎn)對(duì)應(yīng)的索引”
// 不存在就返回-1
public int lastIndexOf(Object o) {
    int index = size ;
    if (o==null) {
        for (Entry e = header .previous; e != header; e = e.previous ) {
            index--;
            if (e.element ==null)
                return index;
        }
    } else {
        for (Entry e = header .previous; e != header; e = e.previous ) {
            index--;
            if (o.equals(e.element ))
                return index;
        }
    }
    return -1;
}

public boolean remove(Object o) 一樣缭黔,indexOf查詢?cè)匚挥谌萜鞯乃饕恢玫倨疲际切枰獙?duì)鏈表進(jìn)行遍歷操作,當(dāng)然也就是低效了啦田巴。

判斷容量

/**
 * Returns the number of elements in this list.
 *
 * @return the number of elements in this list
 */
public int size() {
    return size ;
}

/**
 * {@inheritDoc}
 *
 * <p>This implementation returns <tt>size() == 0 </tt>.
 */
public boolean isEmpty() {
    return size() == 0;
}

和ArrayList一樣壹哺,基于計(jì)數(shù)器size操作艘刚,容量判斷很方便。
到這里L(fēng)inkedList就分析完了箩朴,不對(duì)好像還差些什么對(duì)不對(duì)秋度?是什么呢荚斯,就是最開(kāi)始說(shuō)的Deque雙端隊(duì)列,明白了鏈表原理和LinkedList的基本crud操作滥壕,Deque的LinkedList實(shí)現(xiàn)就已經(jīng)是so easy了兽泣,我們簡(jiǎn)單看下。

LinkedList實(shí)現(xiàn)的Deque雙端隊(duì)列

/**
 * Adds the specified element as the tail (last element) of this list.
 *
 * @param e the element to add
 * @return <tt> true</tt> (as specified by {@link Queue#offer})
 * @since 1.5
 */
public boolean offer(E e) {
    return add(e);
}

/**
 * Retrieves and removes the head (first element) of this list
 * @return the head of this list, or <tt>null </tt> if this list is empty
 * @since 1.5
 */
public E poll() {
    if (size ==0)
        return null;
    return removeFirst();
}

/**
 * Removes and returns the first element from this list.
 *
 * @return the first element from this list
 * @throws NoSuchElementException if this list is empty
 */
public E removeFirst() {
    return remove(header .next);
}

/**
 * Retrieves, but does not remove, the head (first element) of this list.
 * @return the head of this list, or <tt>null </tt> if this list is empty
 * @since 1.5
 */
public E peek() {
    if (size ==0)
        return null;
    return getFirst();
}

/**
 * Returns the first element in this list.
 *
 * @return the first element in this list
 * @throws NoSuchElementException if this list is empty
 */
public E getFirst() {
    if (size ==0)
       throw new NoSuchElementException();

    return header .next. element;
}

/**
 * Pushes an element onto the stack represented by this list.  In other
 * words, inserts the element at the front of this list.
 *
 * <p>This method is equivalent to {@link #addFirst}.
 *
 * @param e the element to push
 * @since 1.6
 */
public void push(E e) {
    addFirst(e);
}

/**
 * Inserts the specified element at the beginning of this list.
 *
 * @param e the element to add
 */
public void addFirst(E e) {
   addBefore(e, header.next );
}

看看Deque 的實(shí)現(xiàn)是不是很簡(jiǎn)單,邏輯都是基于上面講的鏈表操作的牵敷,對(duì)于隊(duì)列的一些概念我不打算在這里講枷餐,是因?yàn)楹竺骊?duì)列會(huì)單獨(dú)拿出來(lái)分析啦苫亦,這里只要理解基于鏈表實(shí)現(xiàn)的list內(nèi)部是怎么操作的就可以啦怨咪。


總結(jié)
(01) LinkedList 實(shí)際上是通過(guò)雙向鏈表去實(shí)現(xiàn)的诗眨。
它包含一個(gè)非常重要的內(nèi)部類:Entry孕讳。Entry是雙向鏈表節(jié)點(diǎn)所對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),它包括的屬性有:當(dāng)前節(jié)點(diǎn)所包含的值芋簿,上一個(gè)節(jié)點(diǎn)璃饱,下一個(gè)節(jié)點(diǎn)荚恶。
(02) 從LinkedList的實(shí)現(xiàn)方式中可以發(fā)現(xiàn),它不存在LinkedList容量不足的問(wèn)題谒撼。
(03) LinkedList的克隆函數(shù)廓潜,即是將全部元素克隆到一個(gè)新的LinkedList對(duì)象中。
(04) LinkedList實(shí)現(xiàn)java.io.Serializable叨叙。當(dāng)寫入到輸出流時(shí)堪澎,先寫入“容量”樱蛤,再依次寫入“每一個(gè)節(jié)點(diǎn)保護(hù)的值”;當(dāng)讀出輸入流時(shí)爽醋,先讀取“容量”便脊,再依次讀取“每一個(gè)元素”。
(05) 由于LinkedList實(shí)現(xiàn)了Deque遂赠,而Deque接口定義了在雙端隊(duì)列兩端訪問(wèn)元素的方法跷睦。提供插入、移除和檢查元素的方法烂琴。每種方法都存在兩種形式:一種形式在操作失敗時(shí)拋出異常蜕乡,另一種形式返回一個(gè)特殊值(null 或 false异希,具體取決于操作)。

對(duì)LinkedList以及ArrayList的迭代效率比較

先說(shuō)結(jié)論:ArrayList使用最普通的for循環(huán)遍歷比較快扣癣,LinkedList使用foreach循環(huán)比較快憨降。

看一下兩個(gè)List的定義:

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

注意到ArrayList是實(shí)現(xiàn)了RandomAccess接口而LinkedList則沒(méi)有實(shí)現(xiàn)這個(gè)接口授药,關(guān)于RandomAccess這個(gè)接口的作用,看一下JDK API上的說(shuō)法:



為此莱衩,我寫一段代碼證明一下這一點(diǎn)娇澎,注意,雖然上面的例子用的Iterator括细,但是做foreach循環(huán)的時(shí)候戚啥,編譯器默認(rèn)會(huì)使用這個(gè)集合的Iterator。測(cè)試代碼如下:

public class TestMain
{
    private static int SIZE = 111111;
    
    private static void loopList(List<Integer> list)
    {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++)
        {
            list.get(i);
        }
        System.out.println(list.getClass().getSimpleName() + "使用普通for循環(huán)遍歷時(shí)間為" + 
                (System.currentTimeMillis() - startTime) + "ms");
        
        startTime = System.currentTimeMillis();
        for (Integer i : list)
        {
            
        }
        System.out.println(list.getClass().getSimpleName() + "使用foreach循環(huán)遍歷時(shí)間為" + 
                (System.currentTimeMillis() - startTime) + "ms");
    }
    
    public static void main(String[] args)
    {
        List<Integer> arrayList = new ArrayList<Integer>(SIZE);
        List<Integer> linkedList = new LinkedList<Integer>();
        
        for (int i = 0; i < SIZE; i++)
        {
            arrayList.add(i);
            linkedList.add(i);
        }
        
        loopList(arrayList);
        loopList(linkedList);
        System.out.println();
    }
}

我截取三次運(yùn)行結(jié)果:

ArrayList使用普通for循環(huán)遍歷時(shí)間為10ms
ArrayList使用foreach循環(huán)遍歷時(shí)間為36ms
LinkedList使用普通for循環(huán)遍歷時(shí)間為21841ms
LinkedList使用foreach循環(huán)遍歷時(shí)間為34ms
ArrayList使用普通for循環(huán)遍歷時(shí)間為11ms
ArrayList使用foreach循環(huán)遍歷時(shí)間為27ms
LinkedList使用普通for循環(huán)遍歷時(shí)間為20500ms
LinkedList使用foreach循環(huán)遍歷時(shí)間為27ms
ArrayList使用普通for循環(huán)遍歷時(shí)間為10ms
ArrayList使用foreach循環(huán)遍歷時(shí)間為22ms
LinkedList使用普通for循環(huán)遍歷時(shí)間為20237ms
LinkedList使用foreach循環(huán)遍歷時(shí)間為38ms

有了JDK API的解釋呆盖,這個(gè)結(jié)果并不讓人感到意外絮短,最最想要提出的一點(diǎn)是:如果使用普通for循環(huán)遍歷LinkedList昨忆,其遍歷速度將慢得令人發(fā)指杉允。

總結(jié)

ArrayList和LinkedList的比較

1叔磷、順序插入速度ArrayList會(huì)比較快,因?yàn)锳rrayList是基于數(shù)組實(shí)現(xiàn)的繁疤,數(shù)組是事先new好的秕狰,只要往指定位置塞一個(gè)數(shù)據(jù)就好了鸣哀;LinkedList則不同,每次順序插入的時(shí)候LinkedList將new一個(gè)對(duì)象出來(lái)叹放,如果對(duì)象比較大挠羔,那么new的時(shí)間勢(shì)必會(huì)長(zhǎng)一點(diǎn)破加,再加上一些引用賦值的操作,所以順序插入LinkedList必然慢于ArrayList

2速那、基于上一點(diǎn)尿背,因?yàn)長(zhǎng)inkedList里面不僅維護(hù)了待插入的元素,還維護(hù)了Entry的前置Entry和后繼Entry荔烧,如果一個(gè)LinkedList中的Entry非常多鹤竭,那么LinkedList將比ArrayList更耗費(fèi)一些內(nèi)存

3、數(shù)據(jù)遍歷的速度吝岭,看最后一部分吧寺,這里就不細(xì)講了稚机,結(jié)論是:使用各自遍歷效率最高的方式,ArrayList的遍歷效率會(huì)比LinkedList的遍歷效率高一些

4失乾、有些說(shuō)法認(rèn)為L(zhǎng)inkedList做插入和刪除更快纬乍,這種說(shuō)法其實(shí)是不準(zhǔn)確的:

(1)LinkedList做插入蕾额、刪除的時(shí)候,慢在尋址退个,快在只需要改變前后Entry的引用地址

(2)ArrayList做插入调炬、刪除的時(shí)候缰泡,慢在數(shù)組元素的批量copy,快在尋址

所以缠借,如果待插入宜猜、刪除的元素是在數(shù)據(jù)結(jié)構(gòu)的前半段尤其是非骋逃担靠前的位置的時(shí)候渠鸽,LinkedList的效率將大大快過(guò)ArrayList徽缚,因?yàn)锳rrayList將批量copy大量的元素革屠;越往后,對(duì)于LinkedList來(lái)說(shuō)红省,因?yàn)樗请p向鏈表,所以在第2個(gè)元素后面插入一個(gè)數(shù)據(jù)和在倒數(shù)第2個(gè)元素后面插入一個(gè)元素在效率上基本沒(méi)有差別麻诀,但是ArrayList由于要批量copy的元素越來(lái)越少傲醉,操作速度必然追上乃至超過(guò)LinkedList

從這個(gè)分析看出呻引,如果你十分確定你插入逻悠、刪除的元素是在前半段韭脊,那么就使用LinkedList沪羔;如果你十分確定你刪除、刪除的元素在比較靠后的位置琅豆,那么可以考慮使用ArrayList篓吁。如果你不能確定你要做的插入越除、刪除是在哪兒呢外盯?那還是建議你使用LinkedList吧饱苟,因?yàn)橐粊?lái)LinkedList整體插入狼渊、刪除的執(zhí)行效率比較穩(wěn)定狈邑,沒(méi)有ArrayList這種越往后越快的情況;二來(lái)插入元素的時(shí)候糕伐,弄得不好ArrayList就要進(jìn)行一次擴(kuò)容蘸嘶,記住训唱,ArrayList底層數(shù)組擴(kuò)容是一個(gè)既消耗時(shí)間又消耗空間的操作

參考

該文為本人學(xué)習(xí)的筆記赞庶,方便以后自己跳槽前復(fù)習(xí)歧强。參考網(wǎng)上各大帖子宴凉,取其精華整合自己的理解而成弥锄。集合框架源碼面試經(jīng)常會(huì)問(wèn),所以解讀源碼十分必要,希望對(duì)你有用温治。
java提高篇(二三)-----HashMap
Java 集合系列10之 HashMap詳細(xì)介紹(源碼解析)和使用示例
圖解集合4:HashMap

整理的集合框架思維導(dǎo)圖

個(gè)人整理的Java集合框架思維導(dǎo)圖熬荆,動(dòng)態(tài)維護(hù)绸狐。導(dǎo)出的圖片無(wú)法查看備注的一些信息,所以需要源文件的童鞋可以關(guān)注我個(gè)人主頁(yè)上的公眾號(hào)突琳,回復(fù)Java集合框架即可獲取源文件拆融。


一直覺(jué)得自己寫的不是技術(shù)镜豹,而是情懷,一篇篇文章是自己這一路走來(lái)的痕跡泰讽」矫啵靠專業(yè)技能的成功是最具可復(fù)制性的镇眷,希望我的這條路能讓你少走彎路欠动,希望我能幫你抹去知識(shí)的蒙塵惑申,希望我能幫你理清知識(shí)的脈絡(luò)圈驼,希望未來(lái)技術(shù)之巔上有你也有我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萤厅,一起剝皮案震驚了整個(gè)濱河市惕味,隨后出現(xiàn)的幾起案子玉锌,更是在濱河造成了極大的恐慌主守,老刑警劉巖榄融,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剃袍,死亡現(xiàn)場(chǎng)離奇詭異民效,居然都是意外死亡涛救,警方通過(guò)查閱死者的電腦和手機(jī)检吆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蹭沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人咆贬,你說(shuō)我怎么就攤上這事掏缎∶荷保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)忌怎。 經(jīng)常有香客問(wèn)我柔袁,道長(zhǎng)捶索,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任辅甥,我火速辦了婚禮璃弄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疏咐。我一直安慰自己浑塞,他們只是感情好政己,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布歇由。 她就那樣靜靜地躺著沦泌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溅蛉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天厅各,我揣著相機(jī)與錄音预柒,去河邊找鬼宜鸯。 笑死淋袖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焰情。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼充岛!你這毒婦竟也來(lái)了耕蝉?” 一聲冷哼從身側(cè)響起赔硫,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤爪膊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后峦阁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體榔昔,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撒会,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年诵肛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怔檩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓄诽。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仑氛,死狀恐怖闸英,靈堂內(nèi)的尸體忽然破棺而出自阱,到底是詐尸還是另有隱情沛豌,我是刑警寧澤赃额,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布跳芳,位于F島的核電站飞盆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吓歇。R本人自食惡果不足惜孽水,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望城看。 院中可真熱鬧女气,春花似錦、人聲如沸测柠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)轰胁。三九已至,卻和暖如春软吐,著一層夾襖步出監(jiān)牢的瞬間瘩将,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工凹耙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肠仪。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親升薯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子佣谐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容