1. 程式人生 > >在元素的裝載數量明確的時候HashMap的大小應該如何選擇。

在元素的裝載數量明確的時候HashMap的大小應該如何選擇。

HashMap的效能問題。問題如下:

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,已經超過預設載入因子(0.75)的大小了。因此會resize一次,變成256。所以最好的結果還是256。

最後發個參考連結:http://www.iteye.com/topic/539465

另,總結StringBuffer、ArrayList、HashMap的擴容:

StringBuffer:內部實現是一個字元陣列。初始預設大小為16,當然也可以在其構造方法中進行設定。當新新增字元或字串時,發現數組容量不夠。這個時候就需要使用Array.copyOf()方法進行擴充。擴充的新的陣列大小等於,(原始容量*2+2)和(陣列實際字元個數+新增的字元長度)之間的較大值。

ArrayList:內部實現是一個Object的陣列。初始預設大小為0,當然也可以在其構造方法中設定。當新增一個Object時,預設擴充陣列容量為10。然後每次擴充的新的陣列大小等於,(原始容量*3/2)和(陣列的長度+1)之間的較大值。根據每次增加一個Object,可得該情況每次擴充的固定大小為3/2。當初始大小為手動設定的時候,每次擴充的新的陣列大小等於,(原始容量*3/2)和(陣列的長度+1)之間的較大值。

HashMap:內部實現是一個Entry的陣列,預設大小是空的陣列。初始化的容量是16,載入因子是3/4(當陣列元素數量大於總容量的載入因子的時候,擴充陣列)。當預設不是空的陣列時,當達到載入因子的比例的時候,每次擴充初始容量的2倍。