HashMap的數據結構:
- 首先,HashMap中數據的存儲是由數組與鏈表一起實現的。
- 數組是在內存中開闢一段連續的空間,因此,只要知道了數組首個元素的地址,在數組中尋址就會非常容易,其時間複雜度爲O(1)。但是當要插入或刪除數據時,時間複雜度就會變爲O(n)。
- 鏈表是內存中一系列離散的空間,其插入和刪除操作的內存複雜度爲O(1),但是尋址操作的複雜度卻是O(n)。那有沒有一種方法可以結合兩者的優點,即尋址,插入刪除快呢?這個方法就是HashMap。
- 我們看看HashMap的拉鍊式實現方法。如下圖
- Entry是一個static class,其中包含了key和value,也就是鍵值對,另外還包含了一個next的Entry指針。可以看出:Entry就是數組中的元素,每個Entry其實就是一個key-value對,它特有一個指向下一個元素的引用,這就構成了鏈表。
1.HashMap中定義了一個Entry類的數組table
- 其中table數組就是buckets(桶),其中數組和鏈表的數據區保存的就是一個Entry對象,Entry對象中保存的就是HashMap中的鍵值對(key-value)
2.HashMap中兩個重要的屬性:capacity(容量)和load factor(加載因子)
- capacity爲buckets的容量,load factor是衡量buckets填滿程度的比例。如果對迭代性能要求很高的話不要把capacity設置過大,也不要把load factor設置過小。當buckets(桶)填充的數組(即hashmap中元素的個數)大於capacity*load factor時就要把buckets擴充爲原來的兩倍。
3.put函數(存儲)的實現
- 先根據key的hashcode()計算出hash值,並用hash值計算出放在數組中的哪個位置
- 判斷該位置是否爲空,若爲空,則直接調用addEntry()方法將元素加入
- 若該位置不爲空(產生數據衝突),將這個位置上的元素統一向這個位置所連的鏈表後推一格,然後將要加入的元素放在鏈表頭部(頭插法)
- 如果數據衝突導致鏈表過長,就把鏈表轉換成紅黑樹
- 如果新添加對象中的key值與之前添加的Entry對象通過equals比較返回true時,新添加Entry的value將覆蓋集合中原有Entry的value,但key不會覆蓋。(保證key的唯一性,不能重複)
- 如果buckets滿了(超過capacity*load factor),就要resize(在resize的過程,簡單的說就是把buckets擴充爲2倍,之後重新計算數組index,把節點再放到新的buckets中)
4.get函數(獲取)的實現
- 首先計算key的hashcode,找到數組中對應位置的某一元素
- 如果有衝突,通過key的equals方法在對應位置的鏈表中找到需要的元素
- 若爲樹,則在樹中通過key.equals()去查找,O(logn)
- 若爲鏈表,則在鏈表中通過key.equals()去查找,O(n)
- 在java1.8之前的是實現中是用鏈表解決衝突的,在產生碰撞的情況下,進行get時,兩步的時間複雜度是O(1)+O(n)。因此,當碰撞很厲害的時候n很大,O(n)的速度顯然是影響速度的。
- 因此在java1.8中,利用紅黑樹替換鏈表,這樣複雜度就變成了O(1)+O(logn)了,這樣在n很大的時候,能夠比較理想的解決這個問題。
5.案例
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class 測試HashMap {
public static void main(String[] args) {
test1();
}
public static void test1(){
HashMap<String, String> map = new HashMap<>();
map.put("CN", "中華人民共和國");
map.put("US", "美利堅合衆國");
map.put("RU", "俄羅斯");
map.put("FR", "法蘭西共和國");
map.put("JP", "日本");
map.put("CN", "中國");
map.put("aa","俄羅斯");
map.put(null, null);
Set<String> set1 = map.keySet();
for (String i : set1){
System.out.println(i);
}
System.out.println("---------------");
Collection<String> values = map.values();
for (String a : values){
System.out.println(a);
}
System.out.println("---------------");
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> e : set){
System.out.println(e.getKey()+"\t\t"+e.getValue());
}
}
}
aa
null
RU
JP
CN
FR
US
---------------
俄羅斯
null
俄羅斯
日本
中國
法蘭西共和國
美利堅合衆國
---------------
aa 俄羅斯
null null
RU 俄羅斯
JP 日本
CN 中國
FR 法蘭西共和國
US 美利堅合衆國
參考資料: