JAVA集合框架合集
說說List,Set,Map三者的區別?
- List(對付順序的好幫手):List介面儲存一組不唯一(可以有多個元素引用相同的物件),有序的物件
- Set(注重獨一無二的性質):不允許重複的集合。不會有多個元素引用相同的物件。
- Map(用Key來搜尋的專家):使用鍵值對儲存。Map會維護與Key有關聯的值。兩個Key可以引用相同的物件,但Key不能重複,典型的Key是String型別,但也可以是任何物件。
Arraylist 與 LinkedList 區別?
-
1. 是否保證執行緒安全:
ArrayList
和LinkedList
都是不同步的,也就是不保證執行緒安全; -
2. 底層資料結構:
Arraylist
底層使用的是Object
陣列;LinkedList
底層使用的是雙向連結串列資料結構(JDK1.6之前為迴圈連結串列,JDK1.7取消了迴圈。注意雙向連結串列和雙向迴圈連結串列的區別,下面有介紹到!) -
3. 插入和刪除是否受元素位置的影響:①
ArrayList
採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。比如:執行add(E e)
方法的時候,ArrayList
會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element)
)時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ②LinkedList
add(E e)
方法的插入,刪除元素時間複雜度不受元素位置的影響,近似 O(1),如果是要在指定位置i
插入和刪除元素的話((add(int index, E element)
) 時間複雜度近似為o(n))
因為需要先移動到指定位置再插入。 -
4. 是否支援快速隨機訪問:
LinkedList
不支援高效的隨機元素訪問,而ArrayList
支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index)
方法)。 -
5. 記憶體空間佔用:ArrayList的空 間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接後繼和直接前驅以及資料)。
HashMap 和 HashSet區別
如果你看過HashSet
原始碼的話就應該知道:HashSet 底層就是基於 HashMap 實現的。(HashSet 的原始碼非常非常少,因為除了clone()
、writeObject()
、readObject()
是 HashSet 自己不得不實現之外,其他方法都是直接呼叫 HashMap 中的方法。
HashMap | HashSet |
---|---|
實現了Map介面 | 實現Set介面 |
儲存鍵值對 | 僅儲存物件 |
呼叫put() 向map中新增元素 |
呼叫add() 方法向Set中新增元素 |
HashMap使用鍵(Key)計算Hashcode | HashSet使用成員物件來計算hashcode值,對於兩個物件來說hashcode可能相同,所以equals()方法用來判斷物件的相等性, |
HashSet如何檢查重複
當你把物件加入HashSet
時,HashSet會先計算物件的hashcode
值來判斷物件加入的位置,同時也會與其他加入的物件的hashcode值作比較,如果沒有相符的hashcode,HashSet會假設物件沒有重複出現。但是如果發現有相同hashcode值的物件,這時會呼叫equals()
方法來檢查hashcode相等的物件是否真的相同。如果兩者相同,HashSet就不會讓加入操作成功。(摘自我的Java啟蒙書《Head fist java》第二版)
hashCode()與equals()的相關規定:
- 如果兩個物件相等,則hashcode一定也是相同的
- 兩個物件相等,對兩個equals方法返回true
- 兩個物件有相同的hashcode值,它們也不一定是相等的
- 綜上,equals方法被覆蓋過,則hashCode方法也必須被覆蓋
- hashCode()的預設行為是對堆上的物件產生獨特值。如果沒有重寫hashCode(),則該class的兩個物件無論如何都不會相等(即使這兩個物件指向相同的資料)。
==與equals的區別
- ==是判斷兩個變數或例項是不是指向同一個記憶體空間 equals是判斷兩個變數或例項所指向的記憶體空間的值是不是相同
- ==是指對記憶體地址進行比較 equals()是對字串的內容進行比較
- ==指引用是否相同 equals()指的是值是否相同
HashMap的底層實現
JDK1.8之前
JDK1.8 之前HashMap
底層是陣列和連結串列結合在一起使用也就是連結串列雜湊。HashMap 通過 key 的 hashCode 經過擾動函式處理過後得到 hash 值,然後通過 (n - 1) & hash 判斷當前元素存放的位置(這裡的 n 指的是陣列的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鍊法解決衝突。
所謂擾動函式指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函式是為了防止一些實現比較差的 hashCode() 方法 換句話說使用擾動函式之後可以減少碰撞。
JDK 1.8 HashMap 的 hash 方法原始碼:
JDK 1.8 的 hash方法 相比於 JDK 1.7 hash 方法更加簡化,但是原理不變。
HashMap 的長度為什麼是2的冪次方
為了能讓 HashMap 存取高效,儘量較少碰撞,也就是要儘量把資料分配均勻。我們上面也講到了過了,Hash 值的範圍值-2147483648到2147483647,前後加起來大概40億的對映空間,只要雜湊函式對映得比較均勻鬆散,一般應用是很難出現碰撞的。但問題是一個40億長度的陣列,記憶體是放不下的。所以這個雜湊值是不能直接拿來用的。用之前還要先做對陣列的長度取模運算,得到的餘數才能用來要存放的位置也就是對應的陣列下標。這個陣列下標的計算方法是“(n - 1) & hash
”。(n代表陣列長度)。這也就解釋了 HashMap 的長度為什麼是2的冪次方。
這個演算法應該如何設計呢?
我們首先可能會想到採用%取餘的操作來實現。但是,重點來了:“取餘(%)操作中如果除數是2的冪次則等價於與其除數減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”並且採用二進位制位操作 &,相對於%能夠提高運算效率,這就解釋了 HashMap 的長度為什麼是2的冪次方。
HashMap 多執行緒操作導致死迴圈問題
主要原因在於 併發下的Rehash 會造成元素之間會形成一個迴圈連結串列。不過,jdk 1.8 後解決了這個問題,但是還是不建議在多執行緒下使用 HashMap,因為多執行緒下使用 HashMap 還是會存在其他問題比如資料丟失。併發環境下推薦使用 ConcurrentHashMap 。
詳情請檢視:https://coolshell.cn/articles/9606.html
ConcurrentHashMap 和 Hashtable 的區別
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,競爭會越來越激烈效率越低。
集合框架底層資料結構總結
Collection
1. List
- Arraylist:Object陣列
- Vector:Object陣列
- LinkedList:雙向連結串列(JDK1.6之前為迴圈連結串列,JDK1.7取消了迴圈)
2. Set
- HashSet(無序,唯一):基於 HashMap 實現的,底層採用 HashMap 來儲存元素
- LinkedHashSet:LinkedHashSet 繼承於 HashSet,並且其內部是通過 LinkedHashMap 來實現的。有點類似於我們之前說的LinkedHashMap 其內部是基於 HashMap 實現一樣,不過還是有一點點區別的
- TreeSet(有序,唯一):紅黑樹(自平衡的排序二叉樹)
Map
- HashMap:JDK1.8之前HashMap由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是主要為了解決雜湊衝突而存在的(“拉鍊法”解決衝突)。JDK1.8以後在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間
- LinkedHashMap:LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基於拉鍊式雜湊結構即由陣列和連結串列或紅黑樹組成。另外,LinkedHashMap 在上面結構的基礎上,增加了一條雙向連結串列,使得上面的結構可以保持鍵值對的插入順序。同時通過對連結串列進行相應的操作,實現了訪問順序相關邏輯。詳細可以檢視:《LinkedHashMap 原始碼詳細分析(JDK1.8)》
- Hashtable:陣列+連結串列組成的,陣列是 HashMap 的主體,連結串列則是主要為了解決雜湊衝突而存在的
- TreeMap:紅黑樹(自平衡的排序二叉樹)
如何選用集合?
主要根據集合的特點來選用,比如我們需要根據鍵值獲取到元素值時就選用Map介面下的集合,需要排序時選擇TreeMap,不需要排序時就選擇HashMap,需要保證執行緒安全就選用ConcurrentHashMap.當我們只需要存放元素值時,就選擇實現Collection介面的集合,需要保證元素唯一時選擇實現Set介面的集合比如TreeSet或HashSet,不需要就選擇實現List介面的比如ArrayList或LinkedList,然後再根據實現這些介面的集合的特點來選用。
來源: JavaGuide
文章作者: SnailClimb
文章連結:https://snailclimb.gitee.io/2019/08/21/java/java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/%E5%85%A8%E7%BD%91%E9%98%85%E8%AF%BB%E8%BF%8720k%E7%9A%84Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93%EF%BC%81/
本文章著作權歸作者所有,任何形式的轉載都請註明出處。