Android的數(shù)據(jù)結(jié)構(gòu)1——List

一個(gè) List 是一個(gè)元素有序的、可以重復(fù)矩乐、可以為 null 的集合花嘶。
Java 集合框架中最常使用的幾種 List 實(shí)現(xiàn)類是 ArrayList,LinkedList 和 Vector柿菩。

集合

在各種 List 中,最好的做法是以 ArrayList 作為默認(rèn)選擇雨涛。 當(dāng)插入枢舶、刪除頻繁時(shí),使用 LinkedList替久,Vector 總是比 ArrayList 慢凉泄,所以要盡量避免使用它,具體實(shí)現(xiàn)后續(xù)文章介紹蚯根。

ArrayList

ArrayList應(yīng)該是接觸集合框架的人第一個(gè)遇到的數(shù)據(jù)結(jié)構(gòu)后众,記得老師當(dāng)年把它叫做可變長(zhǎng)度的數(shù)組,接下來(lái)就布置了作業(yè),讓大家自己實(shí)現(xiàn)可擴(kuò)展長(zhǎng)度的數(shù)組型數(shù)據(jù)結(jié)構(gòu)蒂誉。
當(dāng)時(shí)的我是這么做的:
首先需要幾個(gè)標(biāo)記:總長(zhǎng)度totalNum教藻,當(dāng)前長(zhǎng)度currentNum。然后總長(zhǎng)度總要比實(shí)際長(zhǎng)度的最大值大于一右锨,當(dāng)?shù)扔?時(shí)自動(dòng)擴(kuò)容(其實(shí)就是new一個(gè)新的數(shù)組并拷貝)括堤。作為課代表的自己覺(jué)得也是有點(diǎn)穩(wěn)。


今天我們來(lái)看看ArrayList是怎么實(shí)現(xiàn)的绍移。

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

構(gòu)造函數(shù)中悄窃,將elementData初始化為空,這個(gè)elementData就是其中的一個(gè)數(shù)組了object[] elementData>蹂窖,無(wú)參構(gòu)造方法構(gòu)造的ArrayList的容量默認(rèn)為10轧抗。
private static final int DEFAULT_CAPACITY = 10
接下來(lái)我們直接看add()方法.

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

在執(zhí)行add方法時(shí), 首先調(diào)用了ensureCapacityInternal方法, 我們可以猜到這個(gè)方法是用于數(shù)組的擴(kuò)充的, 接下來(lái)才將傳入的元素放到size處。
跟進(jìn)ensureCapacityInternal恼策,在方法中有各種的判斷鸦致,最后我們可以看到這樣的代碼

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

這里的grow函數(shù)就是實(shí)現(xiàn)數(shù)組擴(kuò)容的方法。
我們看到涣楷,每當(dāng)向數(shù)組中添加元素時(shí)分唾,都要去檢查添加后元素的個(gè)數(shù)是否會(huì)超出當(dāng)前數(shù)組的長(zhǎng)度,如果超出狮斗,數(shù)組將會(huì)進(jìn)行擴(kuò)容绽乔,以滿足添加數(shù)據(jù)的需求,擴(kuò)容數(shù)量為oldCapacity的0.5倍碳褒,即右移1位折砸。數(shù)組擴(kuò)容通過(guò)一個(gè)公開(kāi)的方法ensureCapacity(int minCapacity)來(lái)實(shí)現(xiàn)。在實(shí)際添加大量元素前沙峻,我也可以使用ensureCapacity來(lái)手動(dòng)增加ArrayList實(shí)例的容量睦授,以減少遞增式再分配的數(shù)量。
這里的Arrays.copyOf()是復(fù)制數(shù)組的操作, 會(huì)消耗較多的時(shí)間

此外ArrayList還給我們提供了將底層數(shù)組的容量調(diào)整為當(dāng)前列表保存的實(shí)際元素的大小的功能摔寨。它可以通過(guò)trimToSize方法來(lái)實(shí)現(xiàn)去枷。代碼如下:

public void trimToSize() {  
   modCount++;  
   int oldCapacity = elementData.length;  
   if (size < oldCapacity) {  
       elementData = Arrays.copyOf(elementData, size);  
   }  
    }

由于elementData的長(zhǎng)度會(huì)被拓展,size標(biāo)記的是其中包含的元素的個(gè)數(shù)是复。所以會(huì)出現(xiàn)size很小但elementData.length很大的情況删顶,將出現(xiàn)空間的浪費(fèi)。trimToSize將返回一個(gè)新的數(shù)組給elementData淑廊,元素內(nèi)容保持不變逗余,length和size相同,節(jié)省空間季惩。

Vector

和ArrayList不同录粱,Vector中的操作是線程安全的腻格。其方法大多數(shù)都含有synchronized 關(guān)鍵字, 在多編程操作是建議使用,但是開(kāi)銷略大
來(lái)看看vector的add方法中的增長(zhǎng)方式

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

這里的capacityIncrement 為增長(zhǎng)因子 , 當(dāng)增長(zhǎng)因子大于0時(shí) , 每次擴(kuò)充的長(zhǎng)度為增長(zhǎng)因子 , 當(dāng)為0是 , 默認(rèn)擴(kuò)充為原數(shù)組的2倍.

LinkedList

LinkedList 是一個(gè)雙鏈表,在添加和刪除元素時(shí)具有比ArrayList更好的性能.但在get與set方面弱于ArrayList.當(dāng)然,這些對(duì)比都是指數(shù)據(jù)量很大或者操作很頻繁的情況下的對(duì)比啥繁。

雙鏈表的插入操作和刪除操作, 相對(duì)于數(shù)組來(lái)說(shuō)要簡(jiǎn)單的多 , 只需要挪動(dòng)兩個(gè)指針即可荒叶,但是改數(shù)據(jù)結(jié)構(gòu)相比于ArrayList更占用內(nèi)存,因?yàn)樾枰喑鰞蓚€(gè)指針來(lái)作為前指針和后指針來(lái)維持?jǐn)?shù)據(jù)結(jié)構(gòu)

它還實(shí)現(xiàn)了 Queue 接口,該接口比List提供了更多的方法,包括 offer(),peek(),poll()等方法.


當(dāng)多個(gè)線程對(duì)同一個(gè)集合進(jìn)行操作的時(shí)候输虱,某線程訪問(wèn)集合的過(guò)程中,該集合的內(nèi)容被其他線程所改變(即其它線程通過(guò)add脂凶、remove宪睹、clear等方法,改變了modCount的值)蚕钦;這時(shí)亭病,就會(huì)拋出ConcurrentModificationException異常,產(chǎn)生fail-fast事件嘶居。

這種情況經(jīng)常發(fā)生在請(qǐng)求數(shù)據(jù)之后 , 對(duì)數(shù)據(jù)本身進(jìn)行新一輪的操作. 如果線程沒(méi)有主意好的話 , 會(huì)拋出異常: ConcurrentModificationException. 以前我直接會(huì)在一開(kāi)始就將數(shù)據(jù)結(jié)構(gòu)選擇為CopyOnWriteArrayList , 待會(huì)來(lái)看看這個(gè)數(shù)據(jù)結(jié)構(gòu)到底是什么罪帖。

CopyOnWriteArrayList

CopyOnWrite容器即寫(xiě)時(shí)復(fù)制的容器。

通俗的理解是當(dāng)我們往一個(gè)容器添加元素的時(shí)候邮屁,不直接往當(dāng)前容器添加整袁,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個(gè)新的容器佑吝,然后新的容器里添加元素坐昙,添加完元素之后,再將原容器的引用指向新的容器芋忿。這樣做的好處是我們可以對(duì)CopyOnWrite容器進(jìn)行并發(fā)的讀炸客,而不需要加鎖,因?yàn)楫?dāng)前容器不會(huì)添加任何元素戈钢。所以CopyOnWrite容器也是一種讀寫(xiě)分離的思想痹仙,讀和寫(xiě)不同的容器。
既然是在增刪是不會(huì)拋出異常殉了,我們來(lái)看看它的add方法以及其它會(huì)引起數(shù)組長(zhǎng)度變化的方法都做了什么

public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }

public E remove(int index) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        }
    }

我們可以看到开仰,在添加與移除的方法體中都是用到了synchronized關(guān)鍵字, 也就是同一時(shí)間宣渗,只允許持有鎖的線程去執(zhí)行這段代碼抖所,其他線程會(huì)被阻塞,這樣就達(dá)到了線程安全讀寫(xiě)List痕囱。
在添加的最后都有setArray()方法:

final void setArray(Object[] a) {
        elements = a;
    }  

add函數(shù)中拷貝了原來(lái)的數(shù)組并在最后加上了新元素田轧,并調(diào)用setArray函數(shù)將引用鏈接到新數(shù)組.
其它用法與ArrayList類似,畢竟都是List的實(shí)現(xiàn)類鞍恢。

  • 缺點(diǎn)
  1. 內(nèi)存占用問(wèn)題傻粘。因?yàn)镃opyOnWrite的寫(xiě)時(shí)復(fù)制機(jī)制每窖,所以在進(jìn)行寫(xiě)操作的時(shí)候,內(nèi)存里會(huì)同時(shí)駐扎兩個(gè)對(duì)象的內(nèi)存弦悉,舊的對(duì)象和新寫(xiě)入的對(duì)象(注意:在復(fù)制的時(shí)候只是復(fù)制容器里的引用窒典,只是在寫(xiě)的時(shí)候會(huì)創(chuàng)建新對(duì)象添加到新容器里,而舊容器的對(duì)象還在使用稽莉,所以有兩份對(duì)象內(nèi)存)瀑志。如果這些對(duì)象占用的內(nèi)存比較大,比如說(shuō)200M左右污秆,那么再寫(xiě)入100M數(shù)據(jù)進(jìn)去劈猪,內(nèi)存就會(huì)占用300M,那么這個(gè)時(shí)候很有可能造成頻繁的Yong GC和Full GC良拼。之前我們系統(tǒng)中使用了一個(gè)服務(wù)由于每晚使用CopyOnWrite機(jī)制更新大對(duì)象战得,造成了每晚15秒的Full GC,應(yīng)用響應(yīng)時(shí)間也隨之變長(zhǎng)庸推。
  2. 數(shù)據(jù)一致性問(wèn)題常侦。CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實(shí)時(shí)一致性贬媒。所以如果你希望寫(xiě)入的的數(shù)據(jù)聋亡,馬上能讀到,請(qǐng)不要使用CopyOnWrite容器掖蛤。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杀捻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚓庭,更是在濱河造成了極大的恐慌致讥,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件器赞,死亡現(xiàn)場(chǎng)離奇詭異垢袱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)港柜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)请契,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人夏醉,你說(shuō)我怎么就攤上這事爽锥。” “怎么了畔柔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵氯夷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我靶擦,道長(zhǎng)腮考,這世上最難降的妖魔是什么雇毫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮踩蔚,結(jié)果婚禮上棚放,老公的妹妹穿的比我還像新娘。我一直安慰自己馅闽,他們只是感情好飘蚯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著福也,像睡著了一般孝冒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拟杉,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音量承,去河邊找鬼搬设。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撕捍,可吹牛的內(nèi)容都是我干的拿穴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼忧风,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼默色!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起狮腿,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腿宰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后缘厢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體吃度,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年贴硫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椿每。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡英遭,死狀恐怖间护,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挖诸,我是刑警寧澤汁尺,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站税灌,受9級(jí)特大地震影響均函,放射性物質(zhì)發(fā)生泄漏亿虽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一苞也、第九天 我趴在偏房一處隱蔽的房頂上張望洛勉。 院中可真熱鬧,春花似錦如迟、人聲如沸收毫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)此再。三九已至,卻和暖如春玲销,著一層夾襖步出監(jiān)牢的瞬間输拇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工贤斜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留策吠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓瘩绒,卻偏偏與公主長(zhǎng)得像猴抹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锁荔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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