java hashmap,如果確定只裝載100個元素,new HashMap(?)多少是最佳的,why?
要回答這個問題,首先得知道影響HashMap性能的參數有哪些。咱們翻翻JDK。
在JDK6中是這么描述的:
HashMap的實例有兩個參數影響其性能:初始容量和加載因子。
首先我們來看初始容量和加載因子的定義。
容量是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。
加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度。
當哈希表中條目的數目超過 容量乘加載因子 的時候,則要對該哈希表進行rehash操作,從而哈希表將具有大約兩倍的桶數。(以上摘自JDK6)
HashMap默認的加載因子是0.75 .它在時間和空間成本上尋求了一種折中。
回到本文的問題。根據JDK中的描述,如果這個只裝載100個元素的HashMap沒有特殊的用途,那么為了在時間和空間上達到最佳性能,HashMap的初始容量可以設為
100/0.75 = 133.33。為了防止rehash,向上取整,為134。
但是還有另外一個問題,就是hash碰撞的問題。如果我們將HashMap的容量設置為134,那么如何保證其中的哈希碰撞會比較少呢?
除非重寫hashcode()方法,否則,似乎沒有辦法保證。
那么這里不得不提HashMap如何為元素選擇下標的方法了。
static int indexFor(int h, int length) { return h & (length-1); }
其中h為key哈希后得到的值,length為哈希表的長度。
134-1 = 128 + 6 -1;
那么 length-1的二進制值的最后3位為101;
假設這100個裝載的元素中他們的key在哈希后有得到兩個值(h),他們的二進制值除了低3位之外都相同,而第一個值的低3位為011,第二個值的低3位為001;
這時候進行java的&預算,011 & 101 = 001 ;001 & 101 = 001;
他們的值相等了,那么這個時候就會發生哈希碰撞。
除此之外還有一個更加嚴重的問題,由於在101中第二位是0,那么,無論我們的key在哈希運算之后得到的值h是什么,那么在&運算之后,得到的結果的倒數第二位均為0;
因此,對於hash表所有下標的二進制的值而言,只要低位第二位的值為1,(例如0010,0011,0111,1111)那么這個下標所代表的桶 將一直是空的,因為代碼中的&運算的結果永遠不會產生低位第二位為1的值。這就大大地浪費了空間,同時還增加了哈希碰撞的概率。這無疑會降低 HashMap的效率。
那么如何才能減少這種浪費呢?最佳的方法當然是讓length-1的二進制值全部位均為1.那么length的值是多少合適呢?
沒錯,length=2^n。
只要將hash表的長度設為2的N次方,那么,所有的哈希桶均有被使用的可能。
再次回到提出的問題,與134最靠近的2^n無疑是128。
如果只修改HashMap的長度而不修改HashMap的加載因子的話,HashMap會進行rehash操作,這是一個代價很大的操作,所以不可取。
那么應該選擇的就應該是256。
而由於空間加大和有效利用哈希桶,這時的哈希碰撞將大大降低,因此HashMap的讀取效率會比較高。
所以,最后結論就是:HashMap的大小應該設置為256。
結果的補充:其實在Java中,無論你的HashMap(x)中的x設置為多少,HashMap的大小都是2^n。2^n是大於x的第一個數。因為HashMap的初始化代碼中有以下這行代碼:
int capacity = 1; while (capacity < initialCapacity) capacity <<= 1;
但是這就帶來了一個問題,如果x=100,那么HashMap的初始大小應該是128.但是100/128=0.78,已經超過默認加載因子的大小了。因此會resize一次,變成256。所以最好的結果還是256。