說在前面
在HashMap中,默認創建的數組長度是16,也就是哈希桶個數為16,當添加key-value的時候,會先計算出他們的哈希值(h = hash),然后用return h & (length-1)
就可以算出一個數組下標,這個數組下標就是鍵值對應該存放的位置。
但是,當數據較多的時候,不同鍵值對算出來的hash值相同,而導致最終存放的位置相同,這就是hash沖突,當出現hash沖突的時候,該位置的數據會轉變成鏈表的形式存儲,但是我們知道,數組的存儲空間是連續的,所以可以直接使用下標索引來查取,修改,刪除數據等操作,而且效率很高。而鏈表的存儲空間不是連續的,所以不能使用下標 索引,對每一個數據的操作都要進行從頭到尾的遍歷,這樣會使效率變得很低,特別是當鏈表長度較大的時候。為了防止鏈表長度較大,需要對數組進行動態擴容。
數組擴容需要申請新的內存空間,然后把之前的數據進行遷移,擴容頻繁,需要耗費較多時間,效率降低,如果在使用完一半的時候擴容,空間利用率就很低,如果等快滿了再進行擴容,hash沖突的概率增大!!那么什么時候開始擴容呢???
為了平衡空間利用率和hash沖突(效率),設置了一個加載因子(loadFactor
),並且設置一個擴容臨界值(threshold = DEFAULT_INITIAL_CAPACITY * loadFactor
),就是說當使用了16*0.75=12個數組以后,就會進行擴容,且變為原來的兩倍。
-
為什么加載因子是0.75呢?
先看一段源碼注釋:
Because TreeNodes are about twice the size of regular nodes, we * use them only when bins contain enough nodes to warrant use * (see TREEIFY_THRESHOLD). And when they become too small (due to * removal or resizing) they are converted back to plain bins. In * usages with well-distributed user hashCodes, tree bins are * rarely used. Ideally, under random hashCodes, the frequency of * nodes in bins follows a Poisson distribution * (http://en.wikipedia.org/wiki/Poisson_distribution) with a * parameter of about 0.5 on average for the default resizing * threshold of 0.75, although with a large variance because of * resizing granularity. Ignoring variance, the expected * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / * factorial(k)). The first values are: * * 0: 0.60653066 * 1: 0.30326533 * 2: 0.07581633 * 3: 0.01263606 * 4: 0.00157952 * 5: 0.00015795 * 6: 0.00001316 * 7: 0.00000094 * 8: 0.00000006 * more: less than 1 in ten million
大概意思就是說,在理想情況下,使用隨機哈希碼,節點出現的頻率在hash桶中遵循泊松分布,同時給出了桶中元素個數和概率的對照表。從上面的表中可以看到當桶中元素到達8個的時候,概率已經變得非常小,也就是說用0.75作為加載因子,每個碰撞位置的鏈表長度超過8個的概率達到了一百萬分之一。