Java學(xué)習(xí)之路——Java集合源碼分析(一)

一剑肯、集合的繼承關(guān)系

Collection是繼承自Iterable

二撕氧、什么是Iterable

1 . Iterable是可迭代的意思
2 . Iterable是一個(gè)接口
3 . 我們看一下官方解釋:Implementing this interface allows an object to be the target of the "for-each loop" statement.
簡(jiǎn)單意思就是:實(shí)現(xiàn)此接口息拜,讓對(duì)象允許成為"for-each loop"的目標(biāo)。
相信什么是"for-each"大家都知道

4 . 里面有三個(gè)方法:

  • 1 Iterator<T> iterator();生成一個(gè)迭代器
Iterator<T> iterator();
  • 2 default void forEach(Consumer<? super T> action)蔚舀;根據(jù)action對(duì)內(nèi)部元素進(jìn)行遍歷
 default void forEach(Consumer<? super T> action) {
        //判斷動(dòng)作是否為空
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • 3 創(chuàng)建一個(gè)分割器(Spliterator)附较,這里先不了解吃粒。
default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

那么我們簡(jiǎn)單定義一下,實(shí)現(xiàn)此接口的類會(huì)成為一個(gè)可迭代的容器拒课。

三徐勃、什么是Collection

1 . 通過(guò)注釋我們可以知道Collection是所有集合的根接口
2 . Collection中包含了很多處理容器中各種元素的方法,比如:增加捕发,移除疏旨,比較(兩個(gè)集合),轉(zhuǎn)換(數(shù)組或者其他)

那么我們可以簡(jiǎn)單定義一下扎酷,這個(gè)Collection接口就是給實(shí)現(xiàn)它的容器提供基本規(guī)范的方法檐涝;

四、什么是List

1 .List是List家族的頂級(jí)接口
2 .List是一個(gè)有序集合序列(在我看來(lái)線性表)
3 .從List中的各種方法看來(lái),List就是用來(lái)規(guī)范List家族的接口
4 .List家族的成員都必須實(shí)現(xiàn)這個(gè)接口谁榜,實(shí)現(xiàn)其中的方法幅聘,成為L(zhǎng)ist的一員
5 .List家族有四個(gè)成員ArrayList、LinkList窃植、Victor帝蒿、Stack

這里簡(jiǎn)單總結(jié)一下,這些接口都是用于規(guī)范的巷怜,并沒(méi)有實(shí)現(xiàn)具體邏輯葛超。

五、ArryList是如何實(shí)現(xiàn)的

接下來(lái)我們要看ArryList的源碼了延塑,但是在看源碼之前我們要理清思路绣张,我們重點(diǎn)看什么?

  • 1 ArryList的重要方法关带,添加侥涵,移除,擴(kuò)張宋雏,比較等等還有與其相關(guān)的屬性
  • 2 ArryList的迭代器

1 . ArryList的迭代器芜飘,ArryList中有一個(gè)內(nèi)部類專門實(shí)現(xiàn)了迭代器接口

 private class Itr implements Iterator<E> {}

2 . 雖然List已經(jīng)有了一個(gè)基本的迭代器,但是List對(duì)這個(gè)基本的迭代器的功能并不滿足磨总,所以下面有產(chǎn)生了一個(gè)List專門用的迭代器:

private class ListItr extends Itr implements ListIterator<E> {}

3嗦明、初此之外,ArrayList覆寫(xiě)了父類和List接口的方法舍败,添加招狸,移除,擴(kuò)張邻薯,比較等等裙戏。

  • 1 ArrayList是通過(guò)數(shù)組實(shí)現(xiàn)
  • 2 ArrayList擴(kuò)張是數(shù)組的copyOf()方法,第一次擴(kuò)張數(shù)組大小變?yōu)?0厕诡,后面如果要擴(kuò)張的話累榜,大小變?yōu)橐郧暗?.5倍
  • 3 對(duì)ArrayList的中操作迭代器會(huì)改變ArrayList的成員變量,但是如果只對(duì)ArrayList操作灵嫌,則沒(méi)有影響迭代器
  • 4 ArrayList里面的元素是無(wú)序的(沒(méi)有按大小順序排列)壹罚,他是根據(jù)插入元素的先后順序排列的

源碼分析

ArrayList的源碼比較清晰,這里只討論是如何自動(dòng)擴(kuò)張的寿羞。
我們知道ArrayList是基于數(shù)組的猖凛,數(shù)組有什么不好的地方呢?對(duì)了绪穆,就是一旦生成了就大小固定無(wú)法擴(kuò)展了辨泳,那么ArrayList是如何自動(dòng)擴(kuò)張的呢虱岂?
首先看向add()方法:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

這里有一個(gè)調(diào)用了ensureCapacityInternal()方法,翻譯一下:確保容量?jī)?nèi)部菠红,那這個(gè)方法是干什么的呢第岖?我們跟蹤查看一下。

private void ensureCapacityInternal(int minCapacity) {
        //判斷當(dāng)前使用的數(shù)組是否為空數(shù)組
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //最小的容量為DEFAULT_CAPACITY和size+1的更大者
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

從第二行看起试溯,elementData是一個(gè)全局變量蔑滓,用來(lái)表示ArrayList當(dāng)前正在使用的數(shù)組,當(dāng)前正在使用的數(shù)組是否是空數(shù)組遇绞,如果是空數(shù)組的話键袱,最小容量就是DEFAULT_CAPACITY(10)和size+1的更大者。
好了看看第六行摹闽,又調(diào)用了一個(gè)ensureExplicitCapacity()方法杠纵,解釋一下:確保明確的容量,我們?cè)俑櫜榭垂澈В葧?huì)兒再回來(lái)。

 private void ensureExplicitCapacity(int minCapacity) {
 //ArrayList的結(jié)構(gòu)變化增加了一次
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        //一定會(huì)執(zhí)行
            grow(minCapacity);
    }

modCount是什么铝量? ArrayList 發(fā)生結(jié)構(gòu)變化的次數(shù)倘屹。
接下來(lái)看看第6行:如果最小容量大于當(dāng)前正在使用的數(shù)組的容量,就執(zhí)行 grow()方法慢叨,我們繼續(xù)追蹤grow()方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新的容量是舊的容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
        // 新容量小于最小容量的話纽匙,就把最小容量賦值給新容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //新容量大于數(shù)組最大容量,就把整數(shù)的最大值賦值給新容量
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

重點(diǎn)來(lái)了拍谐,看向第13行烛缔,數(shù)組實(shí)際擴(kuò)張的辦法,通過(guò)Arrays的靜態(tài)方法copyOf()轩拨。我們追蹤一下這個(gè)方法:

 public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

啊哈践瓷,又調(diào)用了它的三個(gè)參數(shù)的重載方法,我們繼續(xù)追蹤:

  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

完成了亡蓉,就是直接new一個(gè)新的數(shù)組晕翠,然后再把舊的數(shù)組的數(shù)據(jù)復(fù)制回去。

接下來(lái)我們要返回到add()方法中完成最后的步驟了:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

給當(dāng)前正在使用的數(shù)elementData添加元素砍濒,這里有個(gè)問(wèn)題淋肾,size中的是什么呢?大家可以猜猜哦爸邢。樊卓。

六、LinkList是如何實(shí)現(xiàn)的

我們先看一下LinkList的繼承關(guān)系

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

我們可以看見(jiàn)LinkList比ArrayList多實(shí)現(xiàn)了一個(gè)接口Deque<E>杠河,那么這個(gè)接口是什么呢碌尔?我們來(lái)看看

public interface Deque<E> extends Queue<E> {}

通過(guò)注釋我們可以發(fā)現(xiàn)這個(gè)接口是一個(gè)線性雙端隊(duì)列浇辜,它支持容量限制的雙端隊(duì)列。
在這里我們對(duì)LinkList有了一點(diǎn)了解七扰。接下來(lái)我們看看LinkList的注釋奢赂。

 * Doubly-linked list implementation of the {@code List} and {@code Deque}
 * interfaces.  Implements all optional list operations, and permits all
 * elements (including {@code null}).

大致意思是:LinkList是一個(gè)雙鏈表,可以操作所有元素

1颈走、LinkList與ArrayList不同膳灶,他是雙鏈表
2、LinkList的擴(kuò)張與刪除就是結(jié)點(diǎn)的增加和刪除
3立由、集合中的元素是也是無(wú)序的轧钓,他是根據(jù)插入結(jié)點(diǎn)的位置來(lái)排列的
4、判斷是從頭結(jié)點(diǎn)插入還是從尾結(jié)點(diǎn)開(kāi)始插入做了優(yōu)化

源碼分析

為什么說(shuō)LinkList是雙鏈表呢锐膜?

 /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

LinkList定義了一個(gè)頭結(jié)點(diǎn)和一個(gè)尾結(jié)點(diǎn)毕箍。Node是LinkList的內(nèi)部類,我們看看他這個(gè)結(jié)點(diǎn)是怎么實(shí)現(xiàn)的:

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是一個(gè)私有的靜態(tài)泛型類道盏,它有三個(gè)全局變量而柑,第一個(gè)變量是結(jié)點(diǎn)儲(chǔ)存的元素,后面兩個(gè)變量分別是當(dāng)前結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)荷逞,很簡(jiǎn)單媒咳。

接下來(lái)我們看看LinkList的插入結(jié)點(diǎn)的方法add()

public boolean add(E e) {
//直接插入默認(rèn)插入到尾部
        linkLast(e);
        return true;
    }
    
      public void add(int index, E element) {
      //檢查這個(gè)索引是否超出鏈表的邊界
        checkPositionIndex(index);

        if (index == size)
        //鏈表為空的時(shí)候或者想要插入到尾部的時(shí)候直接插入到尾部
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

我們可以看見(jiàn) LinkList的add()有兩個(gè)重載方法。

直接插入話种远,默認(rèn)是插入到尾部的涩澡。

如果過(guò)通過(guò)索引插入的話,我們看向第15行:
這里執(zhí)行了linkBefore()方法坠敷,插入之前的查找工作妙同,傳入了需要插入的元素和node()這個(gè)方法的返回值
我們先看看node()這個(gè)方法是干什么的:

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

這里代碼很簡(jiǎn)單,如果請(qǐng)求插入的索引小于size的一半則從頭開(kāi)始遍歷膝迎,反之則從尾部開(kāi)始遍歷粥帚。返回的也是一個(gè)節(jié)點(diǎn)

然后我們看看linkBefore()方法:

 /**
     * Inserts element e before non-null Node succ.//把我們要插入的元素插入到suuc這個(gè)節(jié)點(diǎn)之前
     */
    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++;
    }

這部分代碼的意思就是把我們需要插入的節(jié)點(diǎn)插入到我們用node()方法找到的節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)那里。

好了LinkList的源碼主要也是分析add()方法限次。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茎辐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掂恕,更是在濱河造成了極大的恐慌拖陆,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懊亡,死亡現(xiàn)場(chǎng)離奇詭異依啰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)店枣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門速警,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叹誉,“玉大人,你說(shuō)我怎么就攤上這事闷旧〕せ恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵忙灼,是天一觀的道長(zhǎng)匠襟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)该园,這世上最難降的妖魔是什么酸舍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮里初,結(jié)果婚禮上啃勉,老公的妹妹穿的比我還像新娘。我一直安慰自己双妨,他們只是感情好淮阐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著刁品,像睡著了一般枝嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哑诊,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音及刻,去河邊找鬼镀裤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缴饭,可吹牛的內(nèi)容都是我干的暑劝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颗搂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼担猛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起丢氢,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤傅联,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后疚察,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蒸走,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年貌嫡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了比驻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片该溯。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖别惦,靈堂內(nèi)的尸體忽然破棺而出狈茉,到底是詐尸還是另有隱情,我是刑警寧澤掸掸,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布氯庆,位于F島的核電站,受9級(jí)特大地震影響猾漫,放射性物質(zhì)發(fā)生泄漏点晴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一悯周、第九天 我趴在偏房一處隱蔽的房頂上張望粒督。 院中可真熱鬧,春花似錦禽翼、人聲如沸屠橄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锐墙。三九已至,卻和暖如春长酗,著一層夾襖步出監(jiān)牢的瞬間溪北,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工夺脾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留之拨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓咧叭,卻偏偏與公主長(zhǎng)得像蚀乔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菲茬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355