Java基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)及源碼分析

前言:所有數(shù)據(jù)結(jié)構(gòu)以及源碼相關(guān)內(nèi)容基于jdk1.8版本。對常見的數(shù)據(jù)結(jié)構(gòu)進行解釋以及對常見方法進行源碼解讀年柠。

ArrayList

ArrayList底層數(shù)據(jù)結(jié)構(gòu)較為簡單

// 底層為數(shù)組結(jié)構(gòu) 所有數(shù)據(jù)均存儲在elementData數(shù)組中
transient Object[] elementData;

底層為數(shù)組結(jié)構(gòu)決定了該集合的優(yōu)缺點:

優(yōu)點:

有序,有索引顾复。
可以根據(jù)索引直接獲取對應的元素餐抢,同時數(shù)組結(jié)構(gòu)根據(jù)索引定位元素性能高,因此查詢快宗收。

缺點:

數(shù)組長度是固定的,導致每次add元素都需判斷長度是否滿足亚兄,如果不滿足需要擴容數(shù)組混稽,擴容的過程其實效率比較低(后面會通過源碼分析擴容過程)。
隨機插入或者刪除元素會導致數(shù)據(jù)內(nèi)容大范圍移動审胚,效率低匈勋。

這就是常說的ArrayList查詢快 增刪慢

源碼剖析

構(gòu)造函數(shù)剖析

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

首先,通過源碼注釋可以了解到膳叨,當使用無參構(gòu)造的時候會初始化一個長度為10的空數(shù)據(jù)洽洁,但是實際源碼發(fā)現(xiàn)剛初始化出來的數(shù)組其實長度是0。而真正初始化該數(shù)據(jù)的位置其實是在add方法中菲嘴。后面講到add方法的時候再做詳細說明饿自。

有參構(gòu)造

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
     /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

通過以上源碼我們可以發(fā)現(xiàn),當傳入一個容量長度的時候龄坪,會初始化一個對應長度的數(shù)組昭雌,如果傳入的長度為0則相當于使用了無參構(gòu)造。如果傳入的為負數(shù)則會拋出非法參數(shù)異常悉默。

add(E e)源碼剖析

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 長度動態(tài)判斷以及擴容的方法城豁,是ArrayList的核心方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 長度滿足之后會直接將傳遞進來的數(shù)據(jù) 賦值給size對對應的索引位置。
        // 同時讓當前集合的size+1 注意集合的長度是size抄课,并不是集合中數(shù)組的長度唱星,這里要特別注意
        elementData[size++] = e;
        return true;
    }

以上代碼不難看出雳旅,ArrayList對數(shù)據(jù)的存儲就是把元素放到數(shù)組中。
接下來主要看這個核心擴容的方法代碼间聊。
這段代碼方法較多攒盈,為了方便閱讀,直接通過注釋來解析哎榴。

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 最大數(shù)組長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    // 數(shù)組默認容量長度
    private static final int DEFAULT_CAPACITY = 10;
    
    protected transient int modCount = 0;
   
    // 核心確保數(shù)組容量的方法型豁,主要功能有計算當前所需的容量值,以及如果容量不夠完成擴容操作尚蝌。
    private void ensureCapacityInternal(int minCapacity) {
        // calculateCapacity方法用于計算當前所需容量
        // ensureExplicitCapacity方法用于擴容
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 首先結(jié)合之前看到的無參構(gòu)造的例子來講
        // 無參構(gòu)造初始化的數(shù)組其實就是一個{}空數(shù)組其實就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
        // 因此這里判斷成立迎变。而此時minCapacity = size + 1 默認數(shù)組長度為0,因此minCapacity值為1飘言。
        // 到此 可以看到 如果使用無參構(gòu)造衣形,計算容量的方法返回的值為DEFAULT_CAPACITY = 10。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 此時再看另外一種情況姿鸿,假設(shè)使用有參構(gòu)造初始化的一個長度為20的數(shù)組谆吴,
        // 上面的判斷條件就不滿足,就會直接返回傳遞過來的minCapacity = size + 1 ,
        return minCapacity;
    }

    // 當計算出來當前數(shù)組所需的長度之后執(zhí)行該方法完成數(shù)組的擴容操作
    private void ensureExplicitCapacity(int minCapacity) {
        // modCount該值沒有實際的業(yè)務含義苛预,只要的作用就是為了防止在使用迭代器遍歷的時候?qū)?shù)組做增刪操作句狼,會根據(jù)該值的變化拋出并發(fā)修改異常
        modCount++;
        
        // 當計算出來當前數(shù)組應有的容量比當前數(shù)組現(xiàn)有長度要大的時候來完成數(shù)組的擴容操作,否則不進行任何操作热某。
        // 當使用無參構(gòu)造初始化數(shù)組的時候腻菇,默認數(shù)組長度為0,而計算出來的數(shù)組長度為默認的長度10苫拍,所以這里就會直接調(diào)用grow方法來完成數(shù)據(jù)的擴容操作
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    // 數(shù)組的擴容方法
    private void grow(int minCapacity) {
        // 記錄擴容之前數(shù)組的長度
        int oldCapacity = elementData.length;
        // 根據(jù)之前數(shù)組的長度芜繁,計算擴容之后的數(shù)組長度。這里用到了位運算绒极,如果對位運算有了解的應該不難看出,這個擴容系數(shù)大致就是0.5蔬捷。
        // 假定當前老數(shù)組長度為10 10的二進制為 1010 右移一位之后變成0101 換算回來就是5 垄提, 10+5 = 15.
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 判斷當前傳遞過來數(shù)組的最小應有長度和擴容之后的長度,取較大值周拐。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果當前算出來的擴容長度大于最大數(shù)組長度 Integer.MAX_VALUE - 8(一般不會有這么長的數(shù)組)铡俐,則會使用hugeCapacity方法來限定一個大小。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 最終調(diào)用Arrays工具類的復制方法來完成數(shù)組的擴容妥粟。這個方法會先初始化一個newCapacity長度的數(shù)組审丘,
        // 再把elementData中的數(shù)據(jù)拷貝過去,這里就是比較耗時勾给,耗內(nèi)存的地方滩报。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    // 該方法比較簡單自己閱讀锅知。
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

add(int indec,E e)源碼剖析

    public void add(int index, E element) {
        // 校驗傳入的index合法性方法,如果傳入的索引小于0或者大于當前數(shù)組的size則會拋出索引越界異常
        rangeCheckForAdd(index);
        // 和上面add方法擴容一樣脓钾。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 調(diào)用系統(tǒng)的native方法完成數(shù)組的拷貝操作售睹。
        // 從elementData數(shù)組索引為index的位置開始拷貝,講數(shù)據(jù)拷貝到index+1的位置可训,拷貝的長度為size-index
        // 例如當前數(shù)組內(nèi)容為["a","b","c","d","e","","","","",""]昌妹,此時調(diào)用add(1,"f")方法。
        // 則拷貝之后的內(nèi)容為["a","b","b","c","d","e","","","",""]
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 拷貝完成之后在給對應index位置元素值做一次覆蓋握截,覆蓋之后數(shù)組的值為      ["a","f","b","c","d","e","","","",""]
        // 由此可以看出這種對應索引位置的插入會導致整個數(shù)組發(fā)生拷貝飞崖,大部分數(shù)據(jù)位置發(fā)生移動。
        elementData[index] = element;
        size++;
    }

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

set(int index,E e)源碼剖析

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }       

以上代碼就比較簡單谨胞,就是直接的值覆蓋對應索引位置的值蚜厉,并返回舊值。

remove(int index)源碼剖析

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

和上面的代碼基本類似畜眨,同樣使用System.arraycopy來拷貝元素昼牛,相當于把要刪除的索引位置的元素用后面index+1開始的索引元素整體前移了一位。

remove(E e)源碼剖析

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

這里的移除和之前的一樣都是使用復制的方式來覆蓋內(nèi)容康聂。只不過移除的時候需要整體遍歷元素找到需要移除的值對應的索引贰健。

get方法源碼

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

get方法的源碼非常簡單。直接就是去數(shù)組對應索引的元素恬汁。

到這里ArrayList相關(guān)常用的api源碼差不多就分析完了伶椿。看完源碼后應該更能體會ArrayList查詢快氓侧,增刪慢的原因了脊另。
接下來繼續(xù)來看LinkedList以及Map相關(guān)的一些api。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末约巷,一起剝皮案震驚了整個濱河市偎痛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌独郎,老刑警劉巖踩麦,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異氓癌,居然都是意外死亡谓谦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門贪婉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來反粥,“玉大人,你說我怎么就攤上這事〔哦伲” “怎么了莫湘?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長娜膘。 經(jīng)常有香客問我逊脯,道長,這世上最難降的妖魔是什么竣贪? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任军洼,我火速辦了婚禮,結(jié)果婚禮上演怎,老公的妹妹穿的比我還像新娘匕争。我一直安慰自己,他們只是感情好爷耀,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布甘桑。 她就那樣靜靜地躺著,像睡著了一般歹叮。 火紅的嫁衣襯著肌膚如雪跑杭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天咆耿,我揣著相機與錄音德谅,去河邊找鬼。 笑死萨螺,一個胖子當著我的面吹牛窄做,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慰技,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼椭盏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吻商?” 一聲冷哼從身側(cè)響起掏颊,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎手报,沒想到半個月后蚯舱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡掩蛤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陈肛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揍鸟。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阳藻,到底是詐尸還是另有隱情晰奖,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布腥泥,位于F島的核電站匾南,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛔外。R本人自食惡果不足惜蛆楞,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夹厌。 院中可真熱鬧豹爹,春花似錦、人聲如沸矛纹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽或南。三九已至孩等,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間采够,已是汗流浹背肄方。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吁恍,地道東北人扒秸。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像冀瓦,于是被迫代替她去往敵國和親伴奥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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