HashMap与加载因子/负载因子loadFactor关系
HashMap以<key,value>的方式存放數據,存儲在數組中。通過開散列方法解決沖突,數組中存放的Entry作為單向鏈表的表頭。
Entry的源碼如下:
static class Entry<K,V> implements Map.Entry<K,V> {final K key;V value;Entry<K,V> next;final int hash;//構造、get、set等方法省略public final boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry e = (Map.Entry)o;Object k1 = getKey();Object k2 = e.getKey();if (k1 == k2 || (k1 != null && k1.equals(k2))) {Object v1 = getValue();Object v2 = e.getValue();if (v1 == v2 || (v1 != null && v1.equals(v2)))return true;}return false;}public final int hashCode() {return (key==null ? 0 : key.hashCode()) ^(value==null ? 0 : value.hashCode());}}下面我們通過走讀源碼來逐步探索HashMap的奧秘!!!
一)“發現問題,存有疑問!”
1)構造HashMap中的capacity
capacity為當前HashMap的Entry數組的大小,為什么Entry數組的大小是2的N次方?
// Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity)capacity <<= 1;2)構造HashMap中的loadFactor(裝填因子)
threshold = (int)(capacity * loadFactor);threshold為HashMap的size最大值,注意不是HashMap內部數組的大小。
為什么需要loadFactor,怎么合理的設置loadFactor?
3)HashMap的put
public V put(K key, V value) {if (key == null)return putForNullKey(value);//當key為null時,將其存放在數組的首部:table[0]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;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null; }這里我們關注兩個地方:
1)hash
static int hash(int h) {// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}hash的作用是什么?
2)indexFor
static int indexFor(int h,int length) {return h & (length-1);}indexFor的作用是什么?
二)“解惑,撥云見日!”
key的hashCode經過hash后,為了讓其在table(table為hashMap的entry[])的范圍內,需要再hash一次。這里實際上是采用的“除余法”(h%length)。
源于一個數學規律,就是如果length是2的N次方,那么數h對length的模運算結果等價于a和(length-1)的按位與運算,也就是?h%length?<=> h&(length-1)。
位運算當然比取余效率高,因此這就解釋了:為什么Entry數組的大小是2的N次方?
我們為什么不直接對key的hashCode進行indexFor運算,還要再hash一次呢?
我做了個簡單實驗,如下:
int[] hashcode = new int[] { 100000001,100000011,100000111,100001111,100011111,100111111,101111111,111111111 }; for (int c : hashcode)System.out.println(hash(c));數組hashcode中假定了8個hashcode,若對他們除以10取余,余數都為1,全部沖突!
但是通過hash后,對應的值為:
94441116; 94441110; 94441204; 94440071; 94558923; 94592891; 107664244; 117165177
對上面的值除以10取余分別是:6、0、4、1、3、1、4、7,沖突為2。
可見hash能夠對hashCode分布更均勻,防止一些蹩腳的hash函數!
從上面我們也可以看出取余的時候,高位的影響比較小,例如:1048592、1048832、1052672、1114112、2097152都可以被16整除(余數為0)!
那么我們得想個辦法讓高位也要影響到取余的結果,如是便有了Hash。
詳情參考:http://marystone.iteye.com/blog/709945
對于loadFactor,我也做了個簡單實驗,如下:
public static void main(String[] args) {String s = "a aa aaa b bb bbb c cc ccc d dd ddd e ee eee f ff fff g gg ggg h hh hhh abc bcd cde def efg fgh ghi hij ijk ";String[] ss = s.split(" ");int size = ss.length;//Set<Integer> indexS = new HashSet<Integer>();int conflict = 0;for (int i = 0; i < ss.length; i++) {int index = hash(ss[i].hashCode()) % size;if (indexS.contains(index))conflict++;elseindexS.add(index);}System.out.println("沖突數:"+conflict);}當參與取余的除數為size時,沖突數為13
當參與取余的除數為2*size時,沖突數為9
當參與取余的除數為3*size時,沖突數為6
在hashMap中,size受loadFactor的影響。
極端的想,
如果loadFactor很小很小,那么map中的table需要不斷的擴容,導致除數越來越大,沖突越來越小!
如果loadFactor很大很大,那么當map中table放滿了也不要求擴容,導致沖突越來越多,解決沖突而起的鏈表越來越長!
原文鏈接:http://www.cnblogs.com/huangfox/archive/2012/07/06/2579614.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的HashMap与加载因子/负载因子loadFactor关系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HashMap中提到的散列是什么?
- 下一篇: 几种排序算法的思想