Java集合架構有兩個頂級接口,一個是collection接口,另一個是map接口,hashmap便是map接口的重要實作類。首先看map接口。根據map鍵值對的特性,接口中必然有相關的方法,主要是:
前三個與map的操作有關,後三個與map周遊相關。
在map接口中,還定義了一個entry接口,hashmap實作本質上是一個entry的數組的連結清單。是以entry可以看成是hashmap的基本單元。下面是entry接口的内容,其中最核心的其實就是這三個方法:
是對一個entry的key和value的擷取以及value的修改。
下面是hashmap的實作類:
首先看一下重要的成員變量,
再看一下構造函數:
initialCapacity是一個與hashmap大小相關的參數,但是呢,他并不是最終的大小,可以看到,數組的大小其實是capacity,capacity是一個大于等于initialCapacity的2的次方,這個是通過一個while循環計算得到的,為什麼必須是2的次方後面會說。loadfactor是裝載因子,衡量飽滿度。threashold是一個門檻值,如果大于這個值,就認為hashmap太滿了,碰撞的機率就很大,這時會觸發resize過程,讓hashmap擴容。modcount與多線程的疊代相關。table是entry的數組。
與hash有關的方法:
上述方法用于計算hash值,首先得到key的hashCode,然後再位運算,最終的hash值很均勻,原因不詳。
這個函數用上面得到的hash值計算table的索引,為了讓每一個位置都有可能容納一個entry,我們第一個想到是的是除模運算,但是在計算機中除法的效率很低,這裡使用位運算就大大提高了效率,這裡也解釋了為什麼數組大小是2的次方,因為2的次方減去1以後就是一個全1的二進制數,做and操作就會保證索引的均勻性,否則加入某一位是0,那麼這一位永遠不會再索引中出現。這樣就可以通過key定位table的索引了。
再看get方法
如果key是null,就調用如下方法:
key為null的entry存放在索引為0的位置,但是位置0不一定隻有key為null的value,是以需要周遊位置0的所有entry,傳回key為null的value。如果key不是null,那麼就調用getEntry方法:
使用key定位索引,然後周遊索引裡面的所有entry,找到key相等的value,傳回,為什麼要周遊,因為可能有碰撞。如果沒指定key的entry,傳回null。
下面是put方法:
如果插入key為null的值,會調用如下方法:
從索引0的entry數組周遊,看是否已經存在,如果是就隻是更改value,此時modecount不加,說明更新不會觸發疊代異常,否則就調用addEntry方法,新增一個entry。
增加一個entry需要看容量是否超過了門檻值,如果超過,就需要resize擴容。然後調用createEntry新增一個entry。
這裡就是調用entry的構造函數,讓新的entry成為這個table槽位的第一個entry,其next指針設定為原本的第一個entry,是以新的元素都是插入到隊首的。
那如果key不是null,過程也類似,通過hash值定位索引,然後在對應的table槽位中周遊entry,更新或者添加。過程一樣。
最後看下擴容:
擴容會建立一個容量為原來2倍的table,然後調用下面的方法把原始的entry加入到新的table:
這個transfer過程就是把原本的每一個entry重新計算索引,再添加到隊首的過程。
這邊是hashmap的一些核心的實作。
最後,注意hashmap不是線程安全的,因為比方說某一時刻兩個線程都要put相同的key和value,很可能在map裡面存在兩個一模一樣的entry,兩個都檢測到沒有key,就調用了addentry方法。
hashtable的實作基本上魚hasnmap是一樣的,隻不過對一些方法加了synchroninzed鎖,hashmap是一個hashtable的輕量級實作,在多線程環境下應該使用hashtable而非hashmap,當然也可以使用其他的線程安全級别的map,或者自己封裝一下hashmap。hashtable還有一個卻别就是不支援null的key和value。會報異常。