前言
Java集合估計是我們開發過程中,用的最多的API了,它位於java.util包下,同時支持多線程的集合類位於java.util.concurrent包下。
我們都知道各種數據結構最底層的組成都是數組或者鏈表,其實各種集合類也是基於最基本的數據結構進行封裝,便於各種場景直接使用。
我們可以把集合想象成一個容器,它可以存儲各種對象,擴展和封裝了數組和鏈表,通過算法實現。
有了這些認識,是不是集合也變了沒那麼抽象了,後續將陸續更新各種常見集合類的底層實現。—慢慢更新
------萬變不離其宗,各種集合就像各門各派的招式,但是其本質都是數據結構和算法。
好吧,這是一個不那麼合適的比喻。天下武功,唯快不破,透過事物的本質,方能笑傲江湖…扯遠了,能不被各路面試官虐慘就心滿意足了…
集合框架
Java集合類主要由兩個根接口Collection和Map派生出來的,Collection派生出了三個子接口:List、Set、Queue,這4個接口又分別衍生出了很多的實現類。
孫猴子毫毛一吹,子孫千千萬?
Collection接口的主要子類關係,如下圖所示:
Collection相當於容納了其他對象的引用,而構成了一個集合的對象,整個集合可以有序的,也可以無序的,它本身不能被直接實現,需要去實現它的子接口如:List,Set。
List接口是一個有序的Collection,能夠精準的控制列表中每個元素的插入位置,能夠通過索引來訪問List中的元素(List中的元素位置,類似數組下標,第一個元素位置爲0),允許有相同的元素,且允許null。
常見的實現:ArrayList(查詢快)、LinkedList(增刪快)。傳送門:【Java集合】ArrayList源碼解析、【Java集合】LinkedList源碼解析。
喂喂,不是還有另外一個兒子Vector嗎,對不起,他已經被後浪排在沙灘上了…
Queue接口以隊列實現了Collection,隊列當然滿足一定的隊列順序了,除了優先隊列外,都是滿足FIFO(先進先出)方式排序元素。Queue實現通常不允許插入null元素,因爲null經常被poll方法用做識別隊列是否包含元素。
常見的實現:PriorityQueue(優先隊列),你期待的傳送門還沒開啓…
Set接口具有和Collection完全一樣的接口,只是行爲上不同,Set不保存重複元素,且對象是唯一的。
常見的實現:HashSet(由Hash表實現)、LinkedHashSet(由鏈表和Hash表實現)、TreeSet(有序的Set),你期待的傳送門還沒開啓…
看完Collection:列表,我們再來看看Map:圖,以key-value鍵值對映射關係存儲元素。
Map接口的主要子類關係,如下圖所示:
將key映射到value的對象,不能包含重複元素,每個key最多映射一個value。
Map實現的類較多:HashMap、HashTable、SortedMap、LinkedHashMap、TreeMap等。你期待的傳送門還沒開啓…
看到這裏,應該對Java集合有了整體宏觀的認識,你肯定會問:後宮三千,我該選哪個好呢?…醒醒,天亮了
List和Set的區別
- List接口實例存儲是有序的,可以重複的元素;Set接口實例存儲的是無序的,不重複的數據;
- List和數組類似,可以動態增長,根據實際存儲的數據長度自動增長List的長度。查找元素效率高(直接通過索引),插入刪除效率低(因爲會引起其他元素位置變動);
- Set查詢效率低下,刪除和插入效率高,插入和刪除不會引起元素位置的變化。
常見的集合實現類
- ArrayList:實現了可變大小的數組,可以隨機訪問和遍歷元素;非同步類;動態增長當前長度的50%;插入刪除效率低(會引起其他元素位置變動)。
- LinkedList:主要用於創建鏈表數據結構,沒有同步方法,需要手動加鎖。
- HashSet:不允許出現重複元素,集合中元素是無序的;允許爲null,但最多一個;不同步,需要手動加鎖。
- LinkedHashSet:具有可預知迭代順序的Set接口的哈希表和鏈接列表實現。
- TreeSet:可以實現排序的Set集合。
- HashMap:散列表,存儲的內容是key-value,根據key的HashCode值存儲數據,具有快速訪問速度;最多允許一個記錄的key爲null;不支持線程同步。
- TreeMap:使用了紅黑樹來實現的Map,不同步。
- WeakHashMap:使用弱密鑰的哈希表,不同步。
- LinkedHashMap:使用元素的自然順序對元素進行排序,不同步。
- IdentityHashMap:比較鍵(和值)時使用引用相等代替對象相等。
線程安全集合類
前面講的集合類好像都是非安全的,那麼安全的集合類有哪些呢?
1.CopyOnWriteArrayList:
1)線程安全的List,修改方法(add,set,remove等)都通過集合屬性一個ReentrantLock進行同步,先獲取鎖,才能執行變更操作。但是通過ReentrantLock進行同步只是能保證線程的安全,並不能保持時間上的有序和正確,因爲先申請鎖然後進入休眠等待的線程,並不一定是最先獲取鎖的線程,所以,會在時間順序看,對集合的修改是無序的。
2)對象數組用volatile修飾,其他線程對集合元素數組的修改,能夠在其他線程的每次訪問都是最新值。
3)在對集合元素數組進行修改時,是先拷貝之前的元素數組出一個新元素數組,在新的元素數組上進行修改,修改完畢後在用元素數組替換舊的元素數組,內存消耗大。
2.ConcurrentSkipListSet:
ConcurrentSkipListSet是實現了NavigableSet接口和繼承自AbstractSet的Set集合類,它是線程安全的,內部存儲實際是存在ConcurrentNavigableMap中。內部元素的存儲是有序的,根據創建ConcurrentSkipListSet時提供的Comparator。
3.CopyOnWriteArraySet:
CopyOnWriteArraySet底層實現是CopyOnWriteArrayList,線程安全的,大部分操作和原理是同CopyOnWriteArrayList。
4.ConcurrentHashMap:
ConcurrentHashMap是線程安全的HashMap,實現了ConcurrentMap接口,所以提供了一些原子性和線程安全的集合操作接口。
JDK1.8之後的ConcurrentHashMap的實現和1.7之前已經大不一樣了,保證線程安全的機制從原來的給每個數組segment加鎖方式變成了無鎖的cas操作,特別是擴容方式被重寫了,實現了無鎖情況下多線程參與複製舊存儲元素到新存儲集合上。有一個最重要的不同點就是ConcurrentHashMap不允許key或value爲null值。
5.HashTable:
HashTable是線程安全的HashMap,其實就在修改方法上添加synchronized關鍵字進行同步,同步的粒度太大,性能不佳,不建議使用。
6.ConcurrentSkipListMap:
ConcurrentSkipListMap是基於SkipList存儲結構實現的線程安全的有序Map集合,不支持Null爲value或者爲key,線程安全且有序。
迭代器和比較器
迭代器(Iterator接口)提供了一種方法,來對集合、容器進行遍歷的方式,使你能夠通過循環來得到換刪除集合元素。
ListIterator接口繼承於Iterator接口,允許雙向遍歷列表和修改元素。
比較器(Comparator接口),集合實現Comparator接口,就可以按照你定義的排序方式進行排序。
小結
後續會深入各個集合類的源代碼進行分析學習,敬請期待…
【Java集合】ArrayList源碼解析
【Java集合】LinkedList源碼解析