List

Overview

List族最重要的幾個特點:
  • 有序
  • 允許重復(fù)元素
  • 支持add, remove, set, get
  • 可以隨機訪問元素
Lis族集合類:
image.png
  • List族中玄渗,最主要的三種集合是ArrayList,VectorLinkedList概漱,后面將對這三種類進行分析和比較陌宿。
  • 可以簡單對比一下List和Set
List Set
有序 Y N
重復(fù)元素 Y N
隨機訪問 Y N

ArrayList

Java Platform SE 8的描述:

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

  • 可變長數(shù)組
  • 實現(xiàn)所有l(wèi)ist的操作
  • 允許null
  • 和Vector相似贡未,但是unsynchronized
<span style="color:#ab4642">ArrayList是一種能夠自動擴充長度的數(shù)組宏多。</span>

性能

The size, isEmpty, get, set, iterator, and listIterator operations run in constant time.
The add operation runs in amortized constant time, that is, adding n elements requires O(n) time.

  • ArrayList的性能可以類比<span style="color:#ab4642">數(shù)組</span>的性能瑞凑,在隨機訪問性能好。增、刪操作性能差玩郊。

線程安全

<span style="color:#ab4642">Note that this implementation is not synchronized...</span>

Collections.synchronizedList:

Returns a synchronized (thread-safe) list backed by the specified list. In order to guarantee serial access, it is critical that all access to the backing list is accomplished through the returned list.*

  • 不能依賴fail-fast進行程序同步控制肢执,應(yīng)該對ArrayList進行包裝(Collections.synchronizedList),合法的ArrayList同步操作demo:
 List list = Collections.synchronizedList(new ArrayList());
      ...
  synchronized (list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

Vector

The Vector class implements a growable array of objects. Like an array, it contains components that can be accessed using an integer index.

The iterators returned by this class's iterator and listIterator methods are fail-fast:...

..., If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector.

  • Vector和ArrayList的性能相似译红,最重要的<span style="color:#ab4642">區(qū)別:</span>是:
    • Vector是<span style="color:#ab4642">線程安全</span>的预茄。
    • 擴容機制不一樣,具體可以看后面的擴容代碼解讀:

LinkedList

Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).
All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.

性能:

  • 具有鏈表的特點侦厚,對增加和刪除節(jié)點的操作效率高(特別是表頭或者表尾的操作)耻陕。但是,隨機訪問效率低刨沦。

同步:

The iterators returned by this class's iterator and listIterator methods are fail-fast

  • LinkedList是線程不安全的诗宣,需要包裝:
List list = Collections.synchronizedList(new LinkedList(...));

ArrayList vs Vector vs LinkedList

ArrayList Vector LinkedList
list接口 Y Y Y
deque接口 N N Y
elements 允許null Y Y Y
growable Y Y N
get O(1) O(1) O(n)
set O(1) O(1) O(n)
remove O(n) O(n) O(1)
add O(n) O(n) O(1)
synchronized N Y N
synchronized N Y N
fail-fast Y Y Y

幾個值得深入思考的問題

fail-fast

fail-fast 機制是java集合(Collection)中的一種錯誤機制。ArrayList,Vector, LinkedList 都滿足fail-fast機制想诅。官方文檔中對fail-fast的解釋如下:

fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

  • fail-fast產(chǎn)生的原因就在于程序在對collection(如: ArrayList)進行迭代時梧田,某個線程對該 collection 在結(jié)構(gòu)上對其做了修改,這時迭代器就會拋出 ConcurrentModificationException 異常信息侧蘸,從而產(chǎn)生 fail-fast

要了解fail-fast機制,我們首先要對ConcurrentModificationException 異常有所了解鹉梨。當方法檢測到對象的并發(fā)修改讳癌,但不允許這種修改時就拋出該異常。同時需要注意的是存皂,該異常不會始終指出對象已經(jīng)由不同線程并發(fā)修改晌坤,如果單線程違反了規(guī)則,同樣也有可能會拋出改異常旦袋。

ConcurrentModificationException

  • 容器使用迭代器來進行統(tǒng)一個容器訪問操作骤菠。迭代器本質(zhì)上只是容器的一個視圖,迭代器里存放容器訪問的游標疤孕,以及expectedModCount
  • expectedModCount是迭代器fail-fast機制的關(guān)鍵商乎,
  • 迭代器在操作容器元素前,會checkForComodification祭阀,其實就是檢查expectedModCount==modCount
  • 迭代器在對容器作結(jié)構(gòu)性后鹉戚,會刷新expectedModCount = modCount
  • 換言之专控,如果在迭代器同步到最新的modCount后抹凳,有其他操作修改了容器的modCount,那么checkForComodification就會校驗失敗伦腐,于是就拋出ConcurrentModificationException
 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
        private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

值得一提的時候赢底,當不適用迭代器訪問和操作容器的時候,不會拋出ConcurrentModificationException,但是并不意味著程序正確幸冻。即使粹庞,是迭代器訪問的程序,也有恰好未發(fā)生ConcurrentModificationException的情況嘁扼。

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
注意信粮,迭代器的快速失敗行為無法得到保證,因為一般來說趁啸,不可能對是否出現(xiàn)不同步并發(fā)修改做出任何硬性保證强缘。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException不傅。因此旅掂,為提高這類迭代器的正確性而編寫一個依賴于此異常的程序是錯誤的做法:迭代器的快速失敗行為應(yīng)該僅用于檢測 bug。

  • 不能依賴fail-fast機制來保證程序的正確性访娶。ConcurrentModificationException只適合用來查bug商虐。
fast-fail的測試代碼
  • failFastWorkTest的Thread t1使用迭代器來訪問容器,在訪問過程中崖疤,Thread t2對容器調(diào)用了ArrayList.remove(element)操作秘车,此后,當t1迭代到下一個元素時候劫哼,(next()會檢查modCount, 即checkForComodification)這將引發(fā)fast-fail.

根據(jù)上面描述叮趴,我們可以寫個demo來測試ArrayList的fast-fail:

    @Test
    public void failFastWorkTest() throws InterruptedException, ConcurrentModificationException     {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Iterator<Integer> iterator = arrayList.iterator();
                while(iterator.hasNext()) {
                    int i = iterator.next();
                    logger.info("thread t1 get item : {}", i);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if (i == 3) {
//                        arrayList.remove(i);
                        arrayList.add(i);
                    }

                }
            }
        });

        runThreads(t1, t2);
    }
    private void runThreads(Thread t1, Thread t2) throws InterruptedException {
        logger.info("start thread 1 ");
        t1.start();
        logger.info("start thread 2 ");
        t2.start();
        t1.join();
        logger.info("end thread1 ");
        t2.join();
        logger.info("end thread2 ");
        logger.info("fail fast test done");

        logger.info("finnal array List", arrayList);
    }
  • failFastNoWorkTest的Thread t1使用直接通過ArrayList.get()來訪問容器,在訪問過程中权烧,Thread t2對容器調(diào)用了ArrayList.remove(element)操作眯亦。未使用迭代器,所以沒有modCount校驗般码,所以不會發(fā)生fast-fail妻率。當實際上,這樣的程序是線程不安全的板祝。
 
    @Test
    public void failFastNoWorkTest() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int size = arrayList.size();
                for (int i = 0; i < size; i++) {
                    int value = arrayList.get(i);
                    logger.info("thread 1 run : {}", value);
                    printPrivateField(AbstractList.class, "modCount", arrayList);

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < RUN_TIMES; i++) {

                    if (i == 3) {
                        arrayList.remove(3);
                    }
                    printPrivateField(AbstractList.class, "modCount", arrayList);
                    addElementByList(arrayList, 100);
                }
            }
        });

        logger.info("before start thread ... ");
        printPrivateField(AbstractList.class, "modCount", arrayList);
        new ListTest().runThreads(t1, t2);

    }

capacity

另一個值得研究的知識點是List的自動擴容宫静,主要是ArrayList和Vector。

ArrayList和Vector都是使用數(shù)組(Array)來控制集合中的對象扔字。當你向這兩種類型中增加元素的時候囊嘉,如果元素的數(shù)目超出了內(nèi)部數(shù)組目前的長度它們都需要擴展內(nèi)部數(shù)組的長度,Vector缺省情況下自動增長原來一倍的數(shù)組長度革为,ArrayList是原來的50%,所以最后你獲得的這個集合所占的空間總是比你實際需要的要大扭粱。所以如果你要在集合中保存大量的數(shù)據(jù)那么使用Vector有一些優(yōu)勢,因為你可以通過設(shè)置集合的初始化大小來避免不必要的資源開銷震檩。

Vector


   /**
     * This implements the unsynchronized semantics of ensureCapacity.
     * Synchronized methods in this class can internally call this
     * method for ensuring capacity without incurring the cost of an
     * extra synchronization.
     *
     * @see #ensureCapacity(int)
     */
    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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);
    }

ArrayList:


    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

所以琢蛤,如果需要存儲的數(shù)據(jù)量比較大蜓堕,可以使用Vector,以減少擴容次數(shù)博其。另外套才,Vector可以設(shè)置capacityIncrement,來配置每次擴容的增長量慕淡,比較靈活背伴。

參考鏈接

ArrayList
simpleJava
fail-fast
SynchronizedList和Vector的區(qū)別, by Hollis

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市峰髓,隨后出現(xiàn)的幾起案子傻寂,更是在濱河造成了極大的恐慌,老刑警劉巖携兵,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疾掰,死亡現(xiàn)場離奇詭異,居然都是意外死亡徐紧,警方通過查閱死者的電腦和手機静檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來并级,“玉大人拂檩,你說我怎么就攤上這事〕氨蹋” “怎么了广恢?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呀潭。 經(jīng)常有香客問我,道長至非,這世上最難降的妖魔是什么钠署? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮荒椭,結(jié)果婚禮上谐鼎,老公的妹妹穿的比我還像新娘。我一直安慰自己趣惠,他們只是感情好狸棍,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著味悄,像睡著了一般草戈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侍瑟,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天唐片,我揣著相機與錄音丙猬,去河邊找鬼。 笑死费韭,一個胖子當著我的面吹牛茧球,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播星持,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼抢埋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了督暂?” 一聲冷哼從身側(cè)響起揪垄,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎损痰,沒想到半個月后福侈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡卢未,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年肪凛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辽社。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡伟墙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滴铅,到底是詐尸還是另有隱情戳葵,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布汉匙,位于F島的核電站拱烁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏噩翠。R本人自食惡果不足惜戏自,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伤锚。 院中可真熱鬧擅笔,春花似錦、人聲如沸屯援。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狞洋。三九已至弯淘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吉懊,已是汗流浹背耳胎。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工惯吕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怕午。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓废登,卻偏偏與公主長得像,于是被迫代替她去往敵國和親郁惜。 傳聞我的和親對象是個殘疾皇子堡距,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355