1. 程式人生 > >Java.util.Map詳解

Java.util.Map詳解

Java為資料結構中的對映提供了一個介面Java.util.Map,此介面主要有四個常用的實現類:HashMap、Hashtable、LinkedHashMap和TreeMap。 繼承關係圖為:

下面針對各個實現類的特點做一些說明:

(1)HashMap: 它是根據鍵的hashcode值儲存資料,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。首先了解一下它的儲存結構:

Hashmap實際上是一個數組和連結串列的結合體(在資料結構中,一般稱之為“連結串列雜湊“)。當我們往hashmap中put元素的時候,先根據key的hash值得到這個元素在陣列中的位置(即下標),然後就可以把這個元素放到對應的位置中了。如果這個元素所在的位子上已經存放有其他元素了,那麼在同一個位子上的元素將以連結串列的形式存放,新加入的放在鏈頭

,最先加入的放在鏈尾。從hashmap中get元素時,首先計算key的hashcode,找到陣列中對應位置的某一元素,然後通過key的equals方法在對應位置的連結串列中找到需要的元素。從這裡我們可以想象得到,如果每個位置上的連結串列只有一個元素,那麼hashmap的get效率將是最高的。

那麼如何根據key的hashcode來確定元素存放的位置?我們首先想到的就是把hashcode對陣列長度取模運算,這樣一來,元素的分佈相對來說是比較均勻的。但是,“模”運算的消耗還是比較大的,能不能找一種更快速,消耗更小的方式那?java中時這樣做的:

首先算得key得hashcode值,然後跟陣列的長度-1做一次“與”運算(&)。看上去很簡單,其實比較有玄機。比如陣列的長度是2的4次方,那麼hashcode就會和2的4次方-1做“與”運算。很多人都有這個疑問,為什麼hashmap的陣列初始化大小都是2的次方大小時

,hashmap的效率最高,我以2的4次方舉例,來解釋一下為什麼陣列大小為2的冪時hashmap訪問的效能最高。

例如:假設陣列長度為2^4=16,則length-1表示成二進位制為1111。兩組的hashcode均為8和9,(1111&1000與1111&1001結果都為1000和1001,不會發生碰撞,但是如果陣列長度為15,length-1=1110,則1110&1000與1110&1001結果為1000,發生碰撞)但是很明顯,當它們和1110“與”的時候(),產生了相同的結果,也就是說它們會定位到陣列中的同一個位置上去,這就產生了碰撞,8和9會被放到同一個連結串列上,那麼查詢的時候就需要遍歷這個連結串列,得到8或者9,這樣就降低了查詢的效率。

此外,HashMap繼承自AbstractMap類。HashMap最多隻允許一條記錄的鍵為null,允許多條記錄的值為null。HashMap非執行緒安全,即任一時刻可以有多個執行緒同時寫HashMap,可能會導致資料的不一致。如果需要滿足執行緒安全,可以用 Collections的synchronizedMap方法使HashMap具有執行緒安全的能力,或者使用ConcurrentHashMap(這裡的synchronizedMap和concurrentHashMap下節再行介紹)。

(2)HashTable

Hashtable繼承自Dictionary類 ,它也是無序的,但是Hashtable是執行緒安全的,同步的,即任一時刻只有一個執行緒能寫Hashtable.HashTable中不允許有null的key和value.

(3)LinkedHashMap

LinkedHashMap是Map中常用的有序的兩種實現之一, 它儲存了記錄的插入順序,先進先出。
對於LinkedHashMap而言,它繼承與HashMap,底層使用雜湊表與雙向連結串列來儲存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的連結列表特性。LinkedHashMap採用的hash演算法和HashMap相同,但是它重新定義了陣列中儲存的元素Entry,該Entry除了儲存當前物件的引用外,還儲存了其上一個元素before和下一個元素after的引用,從而在雜湊表的基礎上又構成了雙向連結列表,效果圖如下:

(4)TreeMap

TreeMap實現SortMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。