標籤:
概要
這一章,我們對HashMap進行學習。
我們先對HashMap有個整體認識,然後再學習它的源碼,最後再通過執行個體來學會使用HashMap。內容包括:
第1部分 HashMap介紹
第2部分 HashMap資料結構
第3部分 HashMap源碼解析(基於JDK1.6.0_45)
第3.1部分 HashMap的“拉鏈法”相關內容
第3.2部分 HashMap的建構函式
第3.3部分 HashMap的主要對外介面
第3.4部分 HashMap實現的Cloneable介面
第3.5部分 HashMap實現的Serializable介面
第4部分 HashMap遍曆方式
第5部分 HashMap樣本
轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3310835.html
第1部分 HashMap介紹
HashMap簡介
HashMap 是一個散列表,它儲存的內容是索引值對(key-value)映射。
HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable介面。
HashMap 的實現不是同步的,這意味著它不是安全執行緒的。它的key、value都可以為null。此外,HashMap中的映射不是有序的。
HashMap 的執行個體有兩個參數影響其效能:“初始容量” 和 “載入因子”。容量 是雜湊表中桶的數量,初始容量 只是雜湊表在建立時的容量。載入因子 是雜湊表在其容量自動增加之前可以達到多滿的一種尺度。當雜湊表中的條目數超出了載入因子與當前容量的乘積時,則要對該雜湊表進行 rehash 操作(即重建內部資料結構),從而雜湊表將具有大約兩倍的桶數。
通常,預設載入因子是 0.75, 這是在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設定初始容量時應該考慮到映射中所需的條目數及其載入因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以載入因子,則不會發生 rehash 操作。
HashMap的建構函式
HashMap共有4個建構函式,如下:
// 預設建構函式。HashMap()// 指定“容量大小”的建構函式HashMap(int capacity)// 指定“容量大小”和“載入因子”的建構函式HashMap(int capacity, float loadFactor)// 包含“子Map”的建構函式HashMap(Map<? extends K, ? extends V> map)
HashMap的API
void clear()Object clone()boolean containsKey(Object key)boolean containsValue(Object value)Set<Entry<K, V>> entrySet()V get(Object key)boolean isEmpty()Set<K> keySet()V put(K key, V value)void putAll(Map<? extends K, ? extends V> map)V remove(Object key)int size()Collection<V> values()
第2部分 HashMap資料結構
HashMap的繼承關係
java.lang.Object ? java.util.AbstractMap<K, V> ? java.util.HashMap<K, V>public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { }
HashMap與Map關係如:
可以看出:
(01) HashMap繼承於AbstractMap類,實現了Map介面。Map是"key-value索引值對"介面,AbstractMap實現了"索引值對"的通用函數介面。
(02) HashMap是通過"拉鏈法"實現的雜湊表。它包括幾個重要的成員變數:table, size, threshold, loadFactor, modCount。
table是一個Entry[]數群組類型,而Entry實際上就是一個單向鏈表。雜湊表的"key-value索引值對"都是儲存在Entry數組中的。
size是HashMap的大小,它是HashMap儲存的索引值對的數量。
threshold是HashMap的閾值,用於判斷是否需要調整HashMap的容量。threshold的值="容量*載入因子",當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍。
loadFactor就是載入因子。
modCount是用來實現fail-fast機制的。
第3部分 HashMap源碼解析(基於JDK1.6.0_45)
為了更瞭解HashMap的原理,下面對HashMap源碼代碼作出分析。
在閱讀源碼時,建議參考後面的說明來建立對HashMap的整體認識,這樣更容易理解HashMap。
View Code
說明:
在詳細介紹HashMap的代碼之前,我們需要瞭解:HashMap就是一個散列表,它是通過“拉鏈法”解決雜湊衝突的。
還需要再補充說明的一點是影響HashMap效能的有兩個參數:初始容量(initialCapacity) 和載入因子(loadFactor)。容量 是雜湊表中桶的數量,初始容量只是雜湊表在建立時的容量。載入因子 是雜湊表在其容量自動增加之前可以達到多滿的一種尺度。當雜湊表中的條目數超出了載入因子與當前容量的乘積時,則要對該雜湊表進行 rehash 操作(即重建內部資料結構),從而雜湊表將具有大約兩倍的桶數。
第3.1部分 HashMap的“拉鏈法”相關內容
3.1.1 HashMap資料存放區數組
transient Entry[] table;
HashMap中的key-value都是儲存在Entry數組中的。
3.1.2 資料節點Entry的資料結構
View Code
從中,我們可以看出 Entry 實際上就是一個單向鏈表。這也是為什麼我們說HashMap是通過拉鏈法解決雜湊衝突的。
Entry 實現了Map.Entry 介面,即實現getKey(), getValue(), setValue(V value), equals(Object o), hashCode()這些函數。這些都是基本的讀取/修改key、value值的函數。
第3.2部分 HashMap的建構函式
HashMap共包括4個建構函式
View Code
第3.3部分 HashMap的主要對外介面
3.3.1 clear()
clear() 的作用是清空HashMap。它是通過將所有的元素設為null來實現的。
View Code
3.3.2 containsKey()
containsKey() 的作用是判斷HashMap是否包含key。
public boolean containsKey(Object key) { return getEntry(key) != null;}
containsKey() 首先通過getEntry(key)擷取key對應的Entry,然後判斷該Entry是否為null。
getEntry()的源碼如下:
View Code
getEntry() 的作用就是返回“鍵為key”的索引值對,它的實現源碼中已經進行了說明。
這裡需要強調的是:HashMap將“key為null”的元素都放在table的位置0處,即table[0]中;“key不為null”的放在table的其餘位置!
3.3.3 containsValue()
containsValue() 的作用是判斷HashMap是否包含“值為value”的元素。
View Code
從中,我們可以看出containsNullValue()分為兩步進行處理:第一,若“value為null”,則調用containsNullValue()。第二,若“value不為null”,則尋找HashMap中是否有值為value的節點。
containsNullValue() 的作用判斷HashMap中是否包含“值為null”的元素。
View Code
3.3.4 entrySet()、values()、keySet()
它們3個的原理類似,這裡以entrySet()為例來說明。
entrySet()的作用是返回“HashMap中所有Entry的集合”,它是一個集合。實現代碼如下:
View Code
HashMap是通過拉鏈法實現的散列表。表現在HashMap包括許多的Entry,而每一個Entry本質上又是一個單向鏈表。那麼HashMap遍曆key-value索引值對的時候,是如何逐個去遍曆的呢?
下面我們就看看HashMap是如何通過entrySet()遍曆的。
entrySet()實際上是通過newEntryIterator()實現的。 下面我們看看它的代碼:
View Code
當我們通過entrySet()擷取到的Iterator的next()方法去遍曆HashMap時,實際上調用的是 nextEntry() 。而nextEntry()的實現方式,先遍曆Entry(根據Entry在table中的序號,從小到大的遍曆);然後對每個Entry(即每個單向鏈表),逐個遍曆。
3.3.5 get()
get() 的作用是擷取key對應的value,它的實現代碼如下:
View Code
3.3.6 put()
put() 的作用是對外提供介面,讓HashMap對象可以通過put()將“key-value”添加到HashMap中。
View Code
若要添加到HashMap中的索引值對對應的key已經存在HashMap中,則找到該索引值對;然後新的value取代舊的value,並退出!
若要添加到HashMap中的索引值對對應的key不在HashMap中,則將其添加到該雜湊值對應的鏈表中,並調用addEntry()。
下面看看addEntry()的代碼:
View Code
addEntry() 的作用是新增Entry。將“key-value”插入指定位置,bucketIndex是位置索引。
說到addEntry(),就不得不說另一個函數createEntry()。createEntry()的代碼如下:
View Code
它們的作用都是將key、value添加到HashMap中。而且,比較addEntry()和createEntry()的代碼,我們發現addEntry()多了兩句:
if (size++ >= threshold) resize(2 * table.length);
那它們的區別到底是什麼呢?
閱讀代碼,我們可以發現,它們的使用情景不同。
(01) addEntry()一般用在 新增Entry可能導致“HashMap的實際容量”超過“閾值”的情況下。
例如,我們建立一個HashMap,然後不斷通過put()向HashMap中添加元素;put()是通過addEntry()新增Entry的。
在這種情況下,我們不知道何時“HashMap的實際容量”會超過“閾值”;
因此,需要調用addEntry()
(02) createEntry() 一般用在 新增Entry不會導致“HashMap的實際容量”超過“閾值”的情況下。
例如,我們調用HashMap“帶有Map”的建構函式,它繪將Map的全部元素添加到HashMap中;
但在添加之前,我們已經計算好“HashMap的容量和閾值”。也就是,可以確定“即使將Map中的全部元素添加到HashMap中,都不會超過HashMap的閾值”。
此時,調用createEntry()即可。
3.3.7 putAll()
putAll() 的作用是將"m"的全部元素都添加到HashMap中,它的代碼如下:
View Code
3.3.8 remove()
remove() 的作用是刪除“鍵為key”元素
View Code
第3.4部分 HashMap實現的Cloneable介面
HashMap實現了Cloneable介面,即實現了clone()方法。
clone()方法的作用很簡單,就是複製一個HashMap對象並返回。
View Code
第3.5部分 HashMap實現的Serializable介面
HashMap實現java.io.Serializable,分別實現了串列讀取、寫入功能。
串列寫入函數是writeObject(),它的作用是將HashMap的“總的容量,實際容量,所有的Entry”都寫入到輸出資料流中。
而串列讀取函數是readObject(),它的作用是將HashMap的“總的容量,實際容量,所有的Entry”依次讀出
View Code
第4部分 HashMap遍曆方式
4.1 遍曆HashMap的索引值對
第一步:根據entrySet()擷取HashMap的“索引值對”的Set集合。
第二步:通過Iterator迭代器遍曆“第一步”得到的集合。
// 假設map是HashMap對象// map中的key是String類型,value是Integer類型Integer integ = null;Iterator iter = map.entrySet().iterator();while(iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); // 擷取key key = (String)entry.getKey(); // 擷取value integ = (Integer)entry.getValue();}
4.2 遍曆HashMap的鍵
第一步:根據keySet()擷取HashMap的“鍵”的Set集合。
第二步:通過Iterator迭代器遍曆“第一步”得到的集合。
// 假設map是HashMap對象// map中的key是String類型,value是Integer類型String key = null;Integer integ = null;Iterator iter = map.keySet().iterator();while (iter.hasNext()) { // 擷取key key = (String)iter.next(); // 根據key,擷取value integ = (Integer)map.get(key);}
4.3 遍曆HashMap的值
第一步:根據value()擷取HashMap的“值”的集合。
第二步:通過Iterator迭代器遍曆“第一步”得到的集合。
// 假設map是HashMap對象// map中的key是String類型,value是Integer類型Integer value = null;Collection c = map.values();Iterator iter= c.iterator();while (iter.hasNext()) { value = (Integer)iter.next();}
遍曆測試程式如下:
View Code
第5部分 HashMap樣本
下面通過一個執行個體學習如何使用HashMap
View Code
(某一次)運行結果:
map:{two=7, one=9, three=6}next : two - 7next : one - 9next : three - 6size:3contains key two : truecontains key five : falsecontains value 0 : falsemap:{two=7, one=9}map is empty
轉寄 java資料結構之hashMap詳解