JDK源碼解析集合篇--綜述

Java集合工具包位於Java.util包下,包含了很多常用的數據結構,如數組、鏈表、棧、隊列、集合、哈希表等。學習Java集合框架下大致可以分爲如下五個部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具類(Arrays、Collections)。在JDK1.5後,util包下加入了concurrent包,更加完善了集合框架對於併發多線程情況下的處理。
這裏寫圖片描述
從上圖可以看到,標藍色的是對應接口的抽象類,它是缺省適配器設計模式的實現,提供了默認或一些共同操作,使得繼承了接口的實現類不用實現接口全部的方法。這在前面的設計模式博文中提到過,設計模式–適配器模式(JDK中的應用)
集合類主要分爲兩大類:Collection和Map。在這裏沒有把併發集合類放到一起來討論,併發集合類在後邊再進行討論。上圖中,我們可以看到set集合與map集合的依賴關係,Set接口通常表示一個集合,其中的元素不允許重複(通過hashcode和equals函數保證),常用實現類有HashSet和TreeSet,HashSet是通過Map中的HashMap實現的,而TreeSet是通過Map中的TreeMap實現的。另外,TreeSet還實現了SortedSet接口,因此是有序的集合(集合中的元素要實現Comparable接口,並覆寫Compartor函數才行)。
注意:在上邊的Vector,Stack和Hashtable類是jdk1.0提供的集合類,都是利用synchronized實現的線程安全的,圖中其他類是線程不安全的,但效率較低,後來被ArrayList、LinkedList,HashMap替代了,這可用於沒有線程安全的情景下,當需要保證線程安全時,Collections中有很多靜態方法可以返回各集合類的synchronized版本,即線程安全的版本,如果要用線程安全的結合類,首選Concurrent併發包下的對應的集合類(併發包做了很多鎖優化)。
集合框架用來存儲一系列對象,當jdk1.5沒有出現泛型之前,集合可以存儲的對象類型都默認爲Object類型,在取出時需要進行強制轉換到相應的對象類型。數組的長度在編譯期就確定且無法改變,集合框架最主要的特性就是長度可變的。

Enumeration和Iterator

Iterator是遍歷集合的迭代器(不能遍歷Map,只用來遍歷Collection),Collection的實現類都實現了iterator()函數,它返回一個Iterator對象,用來遍歷集合,ListIterator則專門用來遍歷List。而Enumeration則是JDK1.0時引入的,作用與Iterator相同,但它的功能比Iterator要少,它只能再Hashtable、Vector和Stack中使用。
Enumeration的速度是Iterator的兩倍,也使用更少的內存。Enumeration是非常基礎的,也滿足了基礎的需要。但是,與Enumeration相比,Iterator更加安全,因爲當一個集合正在被遍歷的時候,它會阻止其它線程去修改集合。Iterator在集合框架中實現的是快速失敗fail-fast機制,當在遍歷時候,集合結構被改變時,會拋出ConcurrentModificationException。例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制。
而對Java1.5併發包(java.util.concurrent)包含線程安全集合類,則允許在迭代時修改集合。
拿ArrayList的迭代器實現爲例:

  public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

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

        @SuppressWarnings("unchecked")
        public E next() {
        //首先要檢查是否集合結構被其他線程改變
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            //刪除時,要檢查集合結構是否被其他線程改變
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //使用迭代器刪除元素,改變集合結構時,這一步將expectedModCount
                //重新賦值了,所以在後邊的遍歷調用next,remove方法是不會拋出異常的
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
        //modCount是ArrayList的屬性,如果被其他線程改變其結構時,modCount會發生變化,
        //expectedModCount是在獲得迭代器時的賦值的,在遍歷中,被改變,則兩者是不相等的,所以
        //會拋出ConcurrentModificationException
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

迭代器取代了Java集合框架中的Enumeration。迭代器允許調用者從集合中移除元素(有remove方法),而Enumeration不能做到。爲了使它的功能更加清晰,迭代器方法名已經經過改善。
這有一個點:
在遍歷過程中,要想刪除元素必須是調用迭代器的remove方法纔不會拋出ConcurrentModificationException異常,看remove方法的實現就可以明白,調用迭代器的remove方法,會對expectedModCount重新賦值,而如果直接調用集合地remove方法,如ArrayList的remove方法改變ArrayList集合結構,也就相當於改變了modCount,與多線程改變集合結構的效果一樣,所以會拋出ConcurrentModificationException異常。

Collection/Map

Collection包含了List、Set、Queue三大分支。Collection 繼承了Iterable接口,Iterable此接口的目的:實現了這個接口的集合對象支持迭代,是可迭代的。而Iterator則是迭代器,它就是提供迭代機制的對象,具體如何迭代,都是Iterator接口規範的。
(1)List:1.代表有序、重複的集合。2.像一個數組,可以記住每次添加元素的順序(要以對象的形式來理解),且長度是可變的。3.訪問的時候根據元素的索引值來訪問。
(2)Set:1.代表無序、不可重複的集合。2.就像一個罐子,但元素單獨存在。訪問元素只能根據元素本身來訪問。3.元素不可以重複
(3)Queue:1.代表隊列集合。2.直接對應數據結構的隊列。3.LinkedList類實現了Queue接口,因此我們可以把LinkedList當成Queue來用。
(4)Map:1.是一個映射接口,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。2.AbstractMap是個抽象類,它實現了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承於AbstractMap。3. Hashtable雖然繼承於Dictionary,但它實現了Map接口。
由於集合類的長度都是可變的,所以在每個實現類,如何實現擴容都是重點內容。

這裏寫圖片描述
可以看出:Collection是一個高度抽象出來的集合,包含了集合的基本操作:添加、刪除、清空、遍歷、是否爲空、獲取大小等。Collection接口的所有子類(直接子類和間接子類)都必須實現2種構造函數:不帶參數的構造函數和參數爲Collection的構造函數。帶參數的構造函數可以用來轉換Collection的類型。
抽象類:AbstractCollection,它實現了Collection中除了iterator()和size()之外的所有方法。AbstractCollection的主要作用是方便其他類實現Collection.,比如ArrayList、LinkedList等。它們想要實現Collection接口,通過集成AbstractCollection就已經實現大部分方法了,再實現一下iterator()和size()即可。
在接下來的學習中,會詳細分析相應的實現類的實現。

集合類特點和應用場景總結:
這裏寫圖片描述
List接口中,比較常用的類有三個:ArrayList、Vactor、LinkedList。
ArrayList :線程不安全的,對元素的查詢速度快。
Vector :線程安全的,多了一種取出元素的方式:枚舉(Enumeration),但已被ArrayList取代。
LinkedList :鏈表結構,對元素的增刪速度很快。
Set接口中,比較常用的類有兩個:HashSet、TreeSet:
HashSet:要保證元素唯一性,需要覆蓋掉Object中的equals和hashCode方法(因爲底層是通過這兩個方法來判斷兩個元素是否是同一個)。
TreeSet:以二叉樹的結構對元素進行存儲,可以對元素進行排序。
排序的兩種方式:
1、元素自身具備比較功能,元素實現Comparable接口,覆蓋compareTo方法。
2、建立一個比較器對象,該對象實現Comparator接口,覆蓋compare方法,並將該對象作爲參數傳給TreeSet的構造函數(可以用匿名內部類)。
Map接口其特點是:元素是成對出現的,以鍵和值的形式體現出來,鍵要保證唯一性:常用類有:HashMap,Hashtable ,TreeMap。
HashMap:線程不安全等的,允許存放null鍵null值。
Hashtable:線程安全的,不允許存放null鍵null值。
TreeMap:可以對鍵進行排序(要實現排序方法同TreeSet)。
Collection和Map兩個接口對元素操作的區別:
存入元素:
Collection接口下的實現類通過add方法來完成,而Map下是通過put方法來完成。
取出元素:
Collection接口下:List接口有兩種方式:1、get(腳標);2、通過Iterator迭代方式獲取元素;而Vactor多了一種枚舉(Enumeration)的方式。Set接口通過迭代的方式獲取元素。
Map接口下:先通地keySet獲取鍵的系列,然後通過該系列使用Iterator迭代方式獲取元素值。

發佈了53 篇原創文章 · 獲贊 119 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章