Java中各種集合的異同與使用
Collection< E >是所有集合類的祖先 我們從這個類往下延伸
1.List
不同點:
名稱 | 數據結構 | 安全性 | 插入和刪除(時間複雜度) | getIndex | 內存空間佔用 |
---|---|---|---|---|---|
ArrayList | 數組 | 非同步,不安全 | 首尾O(1),index O(n-i)) | 高效 | 結尾會預留一定的空間 |
LinkedList | 雙向鏈表 | 非同步,不安全 | O(n) | 較慢 | 每一個元素都耗時 |
Vector(淘汰) | 數 組 | 線程安全(synchronized) | ----- | ----- | ----- |
- ArrayList增刪沒有想象中慢,ArrayList的增刪底層調用的copyOf()被優化過,加上現代CPU對內存可以塊操作,普通大小的ArrayList增刪比LinkedList更快。
2.Set
- HashSet
- LinkedHashSet
- TreeSet
不同點:
名稱 | 順序 | 基礎 |
---|---|---|
HashSet | 無序 | 基於HashMap |
LinkedHashSet | 自動排序 | 基於TreeMap |
TreeSet | 維護“插入順序” | 基於HashSet,是HashSet的擴展 |
- Set不允許包含相同的元素,如果試圖把兩個相同元素加入同一個集合中,add方法返回false
3.Map
- Map
- HashMap
- HashTable
- LinkedHashMap
- TreeMap
不同點:
名稱 | 實現 | 繼承 | 是否允許null值 | 擴容方式 | 存儲方式 |
---|---|---|---|---|---|
HashMap | 實現Map接口 | 繼承 AbstractMap 類 | 允許key和value爲null值 | size*2,且size必定爲2^n | 1.如果衝突數量於8,則是以鏈表方式解決衝突。2.如果當衝突大於等於8時,就會將衝突的Entry轉換爲紅黑樹進行存儲。3如果當數量小於6時,則又轉化爲鏈表存儲。 |
HashTable | 實現Map接口 | 繼承 Dictionary類 | 鍵值對都不能爲空 | size*2+1 | 鏈表方式存儲 |
LinkedHashMap | 實現Map接口 | 繼承 HashMap類 | 允許key和value爲null值 | 基於拉鍊式散列結構即由數組和鏈表或紅黑樹組成 | |
TreeMap | 實現NavigableMap接口 | 繼承 AbstractMap類 | 不允許Key爲null | 紅黑樹(自平衡的排序二叉樹) |
已上是常見集合的梳理,但是在多線程環境下 , Vector被淘汰 , 使用synchronizedList又過於笨重
4. JUC下常用的幾個線程安全容器
1. CopyOnWriteArrayList
2. CopyOnWriteArraySet
同CopyOnWriteArrayList
3. ConcurrentLinkedQueue
ConcurrentLinkedQueue
4. ConcurrentSkipListMap
ConcurrentSkipListMap
5. ConcurrentHashMap
因爲HashTable也是線程安全的,總結一下不同點:
- 底層數據結構:
- JDK1.7的 ConcurrentHashMap 底層採用 分段的數組+鏈表 實現,JDK1.8 採用的數據結構跟HashMap1.8的結構一樣,數組+鏈表/紅黑二叉樹。
- Hashtable 和 JDK1.8 之前的 HashMap 的底層數據結構類似都是採用 數組+鏈表 的形式,數組是 HashMap 的主體,鏈表則是主要爲了解決哈希衝突而存在的;
- 實現線程安全的方式(重要):
- ① 在JDK1.7的時候,ConcurrentHashMap(分段鎖) 對整個桶數組進行了分割分段(Segment),每一把鎖只鎖容器其中一部分數據,多線程訪問容器裏不同數據段的數據,就不會存在鎖競爭,提高併發訪問率。 到了 JDK1.8 的時候已經摒棄了Segment的概念,而是直接用 Node 數組+鏈表+紅黑樹的數據結構來實現,併發控制使用 synchronized 和 CAS 來操作。(JDK1.6以後 對 synchronized鎖做了很多優化) 整個看起來就像是優化過且線程安全的 HashMap,雖然在JDK1.8中還能看到 Segment 的數據結構,但是已經簡化了屬性,只是爲了兼容舊版本;
- ② Hashtable(同一把鎖) :使用 synchronized 來保證線程安全,效率非常低下。當一個線程訪問同步方 法時,其他線程也訪問同步方法,可能會進入阻塞或輪詢狀態,如使用 put 添加元素,另一個線程不能使用 put 添加元素,也不能使用 get,競爭會越來越激烈效率越低。
如何選用集合?
主要根據集合的特點來選用,比如我們需要根據鍵值獲取到元素值時就選用Map接口下的集合,
需要排序時選擇TreeMap,不需要排序時就選擇HashMap,需要保證線程安全就選用ConcurrentHashMap.
當我們只需要存放元素值時,就選擇實現Collection接口的集合,
需要保證元素唯一時選擇實現Set接口的集合比如TreeSet或HashSet,
不需要就選擇實現List接口的比如ArrayList或LinkedList,
然後再根據實現這些接口的集合的特點來選用。