在Java編程語言中,最基本的結構就是兩種,一種是數組,一種是模擬指針(引用),全部的數據結構均可以用這兩個基本結構構造,HashMap也同樣。 當程序試圖將多個 key-value 放入 HashMap 中時,以以下代碼片斷爲例: java
HashMap<String,Object> m=new HashMap<String,Object>();
m.put("a", "rrr1");
m.put("b", "tt9");
m.put("c", "tt8");
m.put("d", "g7");
m.put("e", "d6");
m.put("f", "d4");
m.put("g", "d4");
m.put("h", "d3");
m.put("i", "d2");
m.put("j", "d1");
m.put("k", "1");
m.put("o", "2");
m.put("p", "3");
m.put("q", "4");
m.put("r", "5");
m.put("s", "6");
m.put("t", "7");
m.put("u", "8");
m.put("v", "9");算法
HashMap 採用一種所謂的「Hash 算法」來決定每一個元素的存儲位置。當 程序執行 map.put(String,Obect)方法 時,系統將調用String的 hashCode() 方法獲得其 hashCode 值——每一個 Java 對象都有 hashCode() 方法,均可經過該方法得到它的 hashCode 值。獲得這個對象的 hashCode 值以後,系統會根據該 hashCode 值來決定該元素的存儲位置。源碼以下: 編程
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //判斷當前肯定的索引位置是否存在相同hashcode和相同key的元素,若是存在相同的hashcode和相同的key的元素,那麼新值覆蓋原來的舊值,並返回舊值。 //若是存在相同的hashcode,那麼他們肯定的索引位置就相同,這時判斷他們的key是否相同,若是不相同,這時就是產生了hash衝突。 //Hash衝突後,那麼HashMap的單個bucket裏存儲的不是一個 Entry,而是一個 Entry 鏈。 //系統只能必須按順序遍歷每一個 Entry,直到找到想搜索的 Entry 爲止——若是剛好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最先放入該 bucket 中), //那系統必須循環到最後才能找到該元素。 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
上面程序中用到了一個重要的內部接口:Map.Entry,每一個 Map.Entry 其實就是一個 key-value 對。從上面程序中能夠看出:當系統決定存儲 HashMap 中的 key-value 對時,徹底沒有考慮 Entry 中的 value,僅僅只是根據 key 來計算並決定每一個 Entry 的存儲位置。這也說明了前面的結論:咱們徹底能夠把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的存儲位置以後,value 隨之保存在那裏便可.HashMap程序通過我改造,我故意的構造出了hash衝突現象,由於HashMap的初始大小16,可是我在hashmap裏面 放了超過16個元素,而且我屏蔽了它的resize()方法。不讓它去擴容。這時HashMap的底層數組Entry[] table結構以下: 數據結構
Hashmap裏面的bucket出現了單鏈表的形式,散列表要解決的一個問題就是散列值的衝突問題,一般是兩種方法:鏈表法和開放地址法。鏈表法就是將 相同hash值的對象組織成一個鏈表放在hash值對應的槽位;開放地址法是經過一個探測算法,當某個槽位已經被佔據的狀況下繼續查找下一個能夠使用的槽 位。java.util.HashMap採用的鏈表法的方式,鏈表是單向鏈表。造成單鏈表的核心代碼以下: 編程語言
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
上面方法的代碼很簡單,但其中包含了一個設計:系統老是將新添加的 Entry 對象放入 table 數組的 bucketIndex 索引處——若是 bucketIndex 索引處已經有了一個 Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象(產生一個 Entry 鏈),若是 bucketIndex 索引處沒有 Entry 對象,也就是上面程序代碼的 e 變量是 null,也就是新放入的 Entry 對象指向 null,也就是沒有產生 Entry 鏈。
spa
HashMap裏面沒有出現hash衝突時,沒有造成單鏈表時,hashmap查找元素很快,get()方法可以直接定位到元素,可是出現單鏈表後,單個 bucket 裏存儲的不是一個 Entry,而是一個 Entry 鏈,系統只能必須按順序遍歷每一個 Entry,直到找到想搜索的 Entry 爲止——若是剛好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最先放入該 bucket 中),那系統必須循環到最後才能找到該元素。 設計
當建立 HashMap 時,有一個默認的負載因子(load factor),其默認值爲 0.75,這是時間和空間成本上一種折衷:增大負載因子能夠減小 Hash 表(就是那個 Entry 數組)所佔用的內存空間,但會增長查詢數據的時間開銷,而查詢是最頻繁的的操做(HashMap 的 get() 與 put() 方法都要用到查詢);減少負載因子會提升數據查詢的性能,但會增長 Hash 表所佔用的內存空間。 指針