1. 程式人生 > >java集合框架08——HashMap和原始碼分析

java集合框架08——HashMap和原始碼分析

        上一章總體分析了Map架構,並簡單分析了一下AbstractMap原始碼,這一章開始我們將對Map的具體實現類進行詳細的學習。本章先研究HashMap。依然遵循以下步驟:先對HashMap有個整體的認識,然後學習它的原始碼,深入剖析HashMap。

1.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不僅繼承了AbstractMap,而且實現了Map、Cloneable和Serializable介面,所以HashMap也可以序列化。另外,HashMap是非同步的,但是我們可以通過Collections類的靜態方法synchronizedMap獲得執行緒安全的HashMap。即:
Map map = Collections.synchronizedMap(new HashMap());
        下面先總覽一下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的資料結構

2.1儲存結構

        HashMap儲存資料的陣列定義如下,裡面存放的是Entry<K,V>實體:

transient Entry<K,V>[] table
        HashMap的底層主要是基於陣列和連結串列實現的,它之所以有相當快的查詢速度主要是因為它是通過計算雜湊碼來決定儲存位置的。HashMap中主要是通過key的hashCode來計算hash值,然後通過hash值選擇不同的陣列來儲存。只要hashCode相同,計算出來的hash值就一樣,如果儲存物件多了,就有可能不同的物件計算出來的hash值是相同的,這就出現了所謂的hash衝突,解決hash衝突的方法很多,具體可以參見我這篇部落格:
資料結構與演算法07 指雜湊表
。HashMap的底層是通過連結串列來解決hash衝突的。下面看一下它的儲存結構圖:


        圖中紫色部分代表雜湊表,其實就是雜湊陣列,陣列的每個元素都是一個單鏈表的頭結點,連結串列是用來解決hash衝突的,如果不同的key對映到了陣列的同一位置,那麼就將其放入單鏈表中。下面的圖可能在程式碼的角度更能說明問題:


        下面我們閱讀一下陣列中儲存的Entry實體類原始碼。

2.2Entry實體

/**
 * Entry其實是個單向連結串列:它是“HashMap鏈式儲存法”對應的連結串列。
 * 它實現了Map.Entry介面,也就是實現了getKey()、getValue()、setValue(V value)
 * equals(Object o)和hashCode()這些方法。
**/
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next; //指向下一個節點
    int hash;

    /**
     * 構造方法,建立一個Entry
     * 引數:雜湊值h,鍵值k,值v和下一個節點n
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    //判斷兩個Entry是否相等,必須key和value都相等,才返回true
    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public final int hashCode() { //實現hashCode
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

    /**
     * 當向HashMap中新增元素時,即呼叫put(k,v)時,
     * 對已經在HashMap中k位置進行v的覆蓋時,會呼叫此方法
     * 這裡沒做任何處理
     */
    void recordAccess(HashMap<K,V> m) {
    }

    /**
     * 當從HashMap中刪除了一個Entry時,會呼叫該函式
     * 這裡沒做任何處理
     */
    void recordRemoval(HashMap<K,V> m) {
    }
}
        從Entry實體原始碼中可以看出,HashMap其實就是一個儲存Entry的陣列,Entry物件包含了鍵和值,其中next也是一個Entry物件,用來處理hash衝突的,形成一個連結串列。這樣一來,我們對HashMap就有很好的理解了。下面我們詳細分析HashMap中的原始碼。

3. HashMap原始碼分析(基於JDK1.7)

        之前分析原始碼都是將所有原始碼全部貼上來,然後分析部分放到原始碼內部,這樣看起來有點太多,一下子好幾百行原始碼看的有點懵。這章開始採用分段式分析,將原始碼分分類,然後各部分突破,這樣看起來更加清晰明朗。

3.1成員屬性

        先看看HashMap的幾個關鍵屬性:

//預設初始容量是16,必須是2的冪
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量(必須是2的冪且小於2的30次方,傳入容量過大會被這個值替換)
static final int MAXIMUM_CAPACITY = 1 << 30;

//預設載入因子,所謂載入因子是指雜湊表在其容量自動增加之前可以達到多滿的一種尺度
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//儲存Entry的預設空陣列
static final Entry<?,?>[] EMPTY_TABLE = {};

//儲存Entry的陣列,長度為2的冪。HashMap採用拉鍊法實現的,每個Entry的本質是個單向連結串列
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//HashMap的大小,即HashMap儲存的鍵值對數量
transient int size;

//HashMap的閾值,用於判斷是否需要調整HashMap的容量
int threshold;

//載入因子實際大小
final float loadFactor;

//HashMap被修改的次數,用於fail-fast機制
transient int modCount;
        我們主要來看看loadFactor屬性,loadFactor表示Hash表中元素的填滿程度。

        若載入因子設定過大,則填滿的元素越多,無疑空間利用率變高了,但是衝突的機會增加了,衝突的越多,連結串列就會變得越長,那麼查詢效率就會變得更低;

        若載入因子設定過小,則填滿的元素越少,那麼空間利用率變低了,表中資料將變得更加稀疏,但是衝突的機會減小了,這樣連結串列就不會太長,查詢效率變得更高。

        這看起來有點繞口,我舉個簡單的例子,如果陣列容量為100,載入因子設定為80,即裝滿了80個才開始擴容,但是在裝的過程中,可能有很多key對應相同的hash值,這樣就會放到同一個連結串列中(因為沒到80個不能擴容),這樣就會導致很多連結串列都變得很長,也就是說,不同的key對應相同的hash值比陣列填滿到80個更加容易出現。

        但是如果設定載入因子為10,那麼陣列填滿10個就開始擴容了,10個相對來說是很容易填滿的,而且在10個內出現相同的hash值概率比上面的情況要小的多,一旦擴容之後,那麼計算hash值又會跟原來不一樣,就不會再衝突了,這樣保證了連結串列不會很長,甚至就一個表頭都有可能,但是空間利用率很低,因為始終有很多空間沒利用就開始擴容。

        因此,就需要在“減小衝突”和“空間利用率”之間尋找一種平衡,這種平衡就是資料結構中有名的“時-空”矛盾的平衡。如果機器記憶體足夠,並且想要提高查詢速度的話可以將載入因子設定小一點;相反如果機器記憶體緊張,並且對查詢速度沒什麼要求的話可以將載入因子設定大一點。一般我們都使用它的預設值,即0.75

3.2構造方法

      下面看看HashMap的幾個構造方法:

/************************** 建構函式 *******************************/

public HashMap(int initialCapacity, float loadFactor) {//帶有初始容量和載入因子
	//確保容量數字合法
	if (initialCapacity < 0) 
		throw new IllegalArgumentException("Illegal initial capacity: " +
										   initialCapacity);
	if (initialCapacity > MAXIMUM_CAPACITY)
		initialCapacity = MAXIMUM_CAPACITY;
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor: " +
										   loadFactor);

	this.loadFactor = loadFactor;
        //將閾值設定為初始容量,這裡不是真正的閾值,是為了擴充套件table的,後面這個閾值會重新計算
        threshold = initialCapacity; 
	init();//一個空方法用於未來的子物件擴充套件</span></span>
}

public HashMap(int initialCapacity) { //帶有初始容量,載入因子設為預設值
	this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() { //初始容量和載入因子均為預設值
	this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

//構造一個對映關係與指定 Map 相同的新 HashMap
public HashMap(Map<? extends K, ? extends V> m) {
	this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
				  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
	inflateTable(threshold);

	putAllForCreate(m);
}
        我們可以看到,在構造HashMap的時候,如果我們指定了載入因子和初始容量的話就呼叫第一個構造方法,否則就用預設的。預設的初始容量為16載入因子為0.75。

3.3存取方法

        存取部分重點分析一下put和get方法,因為這兩個方法也是最常用的。其他的存取方法,我放到程式碼中分析。首先看看HashMap中是如何儲存資料的,看put方法:

public V put(K key, V value) {
	if (table == EMPTY_TABLE) { //如果雜湊表沒有初始化(table為空)
		inflateTable(threshold); //用構造時的閾值(其實就是初始容量)擴充套件table
	}
	//如果key==null,就將value加到table[0]的位置
	//該位置永遠只有一個value,新傳進來的value會覆蓋舊的value
	if (key == null) 
		return putForNullKey(value);

	int hash = hash(key); //根據鍵值計算hash值

	int i = indexFor(hash, table.length); //搜尋指定hash在table中的索引

	//迴圈遍歷Entry陣列,若該key對應的鍵值對已經存在,則用新的value取代舊的value
	for (Entry<K,V> e = table[i]; e != null; e = e.next) {
		Object k;
		if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
			V oldValue = e.value;
			e.value = value;
			e.recordAccess(this);
			return oldValue; //並返回舊的value
		}
	}

	modCount++;
	//如果在table[i]中沒找到對應的key,那麼就直接在該位置的連結串列中新增此Entry
	addEntry(hash, key, value, i);
	return null;
}
        我們下面一步步分析put方法內部都幹了些啥:

        首先檢測table是不是為空table,如果是空table,說明並沒有給table初始化,所以呼叫inflateTable(threadshold)方法給table初始化。該方法如下:

//擴充套件table
private void inflateTable(int toSize) {
	// Find a power of 2 >= toSize
	int capacity = roundUpToPowerOf2(toSize); //獲取和toSize最接近的2的冪作為容量
	//重新計算閾值 threshold = 容量 * 載入因子
	threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
	table = new Entry[capacity]; //用該容量初始化table
	initHashSeedAsNeeded(capacity);
}
//將初始容量轉變成2的冪
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY //如果容量超過了最大值,設定為最大值
            //否則設定為最接近給定值的2的次冪數
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
        在inflateTable方法內,首先初始化陣列容量大小,陣列容量永遠是2的冪(下面會分析為什麼要這樣)。所以呼叫roundUpToPowerOf2方法將傳進來的容量轉換成最接近2的次冪的值,然後重新計算閾值threadshold = 容量 x 載入因子,最後初始化table。所以剛開始初始化table不是在HashMap的建構函式裡,因為建構函式中僅僅簡單的將傳進去的容量作為閾值。真正初始化table是在第一次往HashMap中put資料的時候

        初始化好了table後,就開始往table中存入資料了,table中存的是Entry實體,而put方法傳進來的是key和value,所以接下來要做兩件事:

        1. 找到table陣列中要存入的位置;

        2. 將key和value封裝到Entry中存入。

        我們再回到put方法中,先來分析第一步,找儲存的位置就要依靠key的值了,因為需要用key的值來計算hash值,根據hash值來決定在table中的位置。首先當key為null時,呼叫putForNullKey方法,該方法內部實現如下:

//傳進key==null的Entry
private V putForNullKey(V value) {
	for (Entry<K,V> e = table[0]; e != null; e = e.next) {
		if (e.key == null) { 
			V oldValue = e.value;
			e.value = value;
			e.recordAccess(this);
			return oldValue;
		}
	}
	modCount++;
	//如果table[0]處沒有key為null
	addEntry(0, null, value, 0);//如果鍵為null的話,則hash值為0
	return null;
}
        從方法中可以看出,null的hash值為0,所以首先會定位到table[0]處,然後依次查詢是否有key==null的鍵,如果有,將對應的value用新的value值取代,同時返回舊的value值。如果沒有key==null的鍵,那麼呼叫addEntry方法,將空鍵和值封裝到Entry中放到table[0]的位置,addEntry方法如下:
//向HashMap中新增Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
	if ((size >= threshold) && (null != table[bucketIndex])) {
		resize(2 * table.length); //擴容2倍
		hash = (null != key) ? hash(key) : 0;
		bucketIndex = indexFor(hash, table.length);
	}

	createEntry(hash, key, value, bucketIndex);
}
//建立一個Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];//先把table中該位置原來的Entry儲存
	//在table中該位置新建一個Entry,將原來的Entry掛到該Entry的next
	table[bucketIndex] = new Entry<>(hash, key, value, e);
	//所以table中的每個位置永遠只儲存一個最新加進來的Entry,其他Entry是一個掛一個,這樣掛上去的
	size++;
}
        從該方法中可以看出,第一個引數是hash值,中間兩個是key和value,最後一個是插入table的索引位置。插入之前先判斷容量是否足夠,若不夠,HashMap中是2倍擴容。若夠了,addEntry中先計算hash值,然後通過呼叫indexFor方法返回在索引的位置,這兩個方法如下:
final int hash(Object k) {
	int h = hashSeed;
	if (0 != h && k instanceof String) {
		return sun.misc.Hashing.stringHash32((String) k);
	}

	h ^= k.hashCode();
        // 預處理hash值,避免較差的離散hash序列,導致table沒有充分利用</span><span>  </span></span>
	h ^= (h >>> 20) ^ (h >>> 12);
	return h ^ (h >>> 7) ^ (h >>> 4);
}
//這個方法有點意思,也是為什麼容量要設定為2的冪的原因
static int indexFor(int h, int length) {
	// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
	return h & (length-1);
}
        indexFor方法返回索引的位置,裡面只做了一件事:h & (length-1)。這究竟做了什麼?為什麼這句能解釋容量必須為2的冪呢?我們詳細分析下:

        首先,h & (length-1)相當於h & length,但是h % length效率比較低(HashTable中是這兒乾的)。為啥h & (length-1)相當於h % length呢?現在假設length為2的冪,那麼length就可以表示成100......00的形式(表示至少1個0),那麼length-1就是01111....11。對於任意小於length的數h來說,與01111...11做&後都是h本身,對於h=length來說,&後結果為0,對於大於length的數h,&過後相當於h-j*length,也就是h % length。這也就是為啥容量必須為2的冪了,為了優化,好做&運算,效率高。

        其次,length為2的次冪的話,是偶數,這樣length-1為奇數,奇數的最後一位是1,這樣便保證了h & (length-1)的最後一位可能為0也可能為1(取決於h的值),即結果可能為奇數,也可能為偶數,這樣便可以保證雜湊的均勻性,即均勻分佈在陣列table中;而如果length為奇數的話,很明顯length-1為偶數,它的最後一位是0,這樣h & (length-1)的最後一位肯定為0,級只能為偶數,這樣任何hash值都會被對映到陣列的偶數下標位置上,這便浪費了近一半的空間!因此,length去2的整數次冪,也是為了使不同hash值發生碰撞的概率較小,這樣就能使元素在雜湊表中均勻的雜湊。

        再回到addEntry方法中,接下來就呼叫createEntry方法在table陣列適當的位置開創一個Entry了,new Entry的時候,將next置為原本在該位置的Entry即可,這樣,原來的Entry就掛到現在的Entry上了,以後只要在該位置新new一個Entry,就將原來的掛上去,這樣一個掛一個,形成了一個連結串列。但是table中永遠儲存的是最新的Entry,並非一個真正的連結串列資料結構,只是這麼多Entry是一個個連在一起的,跟連結串列很像而已。

        現在往上回到put方法,我們剛剛分析完了key==null的情況,接著往下走,下面其實跟剛剛分析的一樣了,先計算hash值,然後找到在table中的位置,然後開始判斷是否已經有相同的key的Entry放在那了,如果有,用新的value取代舊的value,如果沒有,用傳進來的key和value新new一個Entry放到table中,並與原來的Entry掛上。過程跟上面分析的一模一樣,唯一不同的就是key!=null。這裡就不再贅述了。

        下面再看看HashMap中讀取資料的get方法:

public V get(Object key) {
	if (key == null)
		return getForNullKey(); //hey==null時,從table[0]中取
	Entry<K,V> entry = getEntry(key);//key!=null->getEntry

	return null == entry ? null : entry.getValue();
}

private V getForNullKey() {
	if (size == 0) {
		return null;
	}
	for (Entry<K,V> e = table[0]; e != null; e = e.next) {
		if (e.key == null)
			return e.value;//從table[0]中取key==null的value值
	}
	return null;
}

final Entry<K,V> getEntry(Object key) {
	if (size == 0) {
		return null;
	}
	//取值與上面put中傳值相反
	int hash = (key == null) ? 0 : hash(key);
	for (Entry<K,V> e = table[indexFor(hash, table.length)];
		 e != null;
		 e = e.next) {
		Object k;
		// 如果hash值相等,並且key相等則證明這個Entry裡的東西是我們想要的
		if (e.hash == hash &&
			((k = e.key) == key || (key != null && key.equals(k))))
			return e;
	}
	return null;
}

        有了對put方法的理解,這段get方法應該就很容易了。從原始碼中更可以看出,從HashMap中get元素時,先計算key的hashCode,找到陣列中國對應的位置,然後通過key的equals在對應位置的連結串列中找到需要的元素。這裡就不再贅述了。

3.4其他方法

        上面我們重點分析了HashMap中的存取方法,當然HashMap中還有其他很多方法,如remove、containsXXX等,以及與迭代器和序列化相關的方法等等。該部分方法的解析全部放在原始碼裡了。原始碼如下:

//返回當前HashMap的key-value對映數,即Entry數量
public int size() {
	return size;
}

//判斷HashMap是否為空,size==0表示空
public boolean isEmpty() {
	return size == 0;
}

//判斷HashMap中是否包含指定鍵的對映
public boolean containsKey(Object key) {
	return getEntry(key) != null; //getEntry方法在上面已經拿出來分析了
}

//看看需不需要建立新的Entry
private void putForCreate(K key, V value) {
	// 如果key為null,則定義hash為0,否則用hash函式預處理  
	int hash = null == key ? 0 : hash(key);
	int i = indexFor(hash, table.length);
	
	//遍歷所有的Entry
	for (Entry<K,V> e = table[i]; e != null; e = e.next) {
		Object k;
		//如果有hash相同,且key相同,那麼則不需要建立新的Entry,退出
		if (e.hash == hash &&
			((k = e.key) == key || (key != null && key.equals(k)))) {
			e.value = value;
			return;
		}
	}

	createEntry(hash, key, value, i);//否則需要建立新的Entry
}

//根據已有的Map建立對應的Entry
private void putAllForCreate(Map<? extends K, ? extends V> m) {
	for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
		putForCreate(e.getKey(), e.getValue());
}

//用新的容量來給table擴容
void resize(int newCapacity) {
	Entry[] oldTable = table; //儲存old table
	int oldCapacity = oldTable.length; //儲存old capacity
	// 如果舊的容量已經是系統預設最大容量了,那麼將閾值設定成整形的最大值,退出  
	if (oldCapacity == MAXIMUM_CAPACITY) {
		threshold = Integer.MAX_VALUE;
		return;
	}

	//根據新的容量新建一個table
	Entry[] newTable = new Entry[newCapacity];
	//將table轉換成newTable
	transfer(newTable, initHashSeedAsNeeded(newCapacity));
	table = newTable;
	//設定閾值
	threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

//將所有的Entry移到新的table中
void transfer(Entry[] newTable, boolean rehash) {
	int newCapacity = newTable.length;
	for (Entry<K,V> e : table) {//獲得原來table中的所有Entry
		while(null != e) {
			Entry<K,V> next = e.next;
			if (rehash) {
				e.hash = null == e.key ? 0 : hash(e.key);
			}
			int i = indexFor(e.hash, newCapacity);
			//下面兩句跟createEntry方法中原理一樣的
			e.next = newTable[i];//設定e.next為newTable[i]儲存的Entry
			newTable[i] = e; //將e設定為newTable[i]
			e = next; //設定e為下一個Entry,繼續上面while迴圈
		}
	}
}

//將指定的Map中所有映射覆制到現有的HashMap中,這些對映關係將覆蓋當前HashMap中針對指定鍵相同的對映關係
//有點拗口,其實就是跟put的原理一樣,key如果存在,就替換現有key對應的value
public void putAll(Map<? extends K, ? extends V> m) {
	//統計下需要複製多少個對映關係
	int numKeysToBeAdded = m.size();
	if (numKeysToBeAdded == 0)
		return;
	//如果table還沒初始化,先用剛剛統計的複製數去初始化table
	if (table == EMPTY_TABLE) {
		inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
	}

	//如果要複製的數目比閾值還要大
	if (numKeysToBeAdded > threshold) {
		//需要先resize
		int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
		if (targetCapacity > MAXIMUM_CAPACITY)
			targetCapacity = MAXIMUM_CAPACITY;
		int newCapacity = table.length;
		while (newCapacity < targetCapacity)
			newCapacity <<= 1;
		if (newCapacity > table.length)
			resize(newCapacity);
	}
	//開始複製
	for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
		put(e.getKey(), e.getValue());//其實就是一個個put進去
}

//根據指定的key刪除Entry,返回對應的value
public V remove(Object key) {
	Entry<K,V> e = removeEntryForKey(key);
	return (e == null ? null : e.value);
}

//根據指定的key,刪除Entry,並返回對應的value
final Entry<K,V> removeEntryForKey(Object key) {
	if (size == 0) {
		return null;
	}
	int hash = (key == null) ? 0 : hash(key);
	int i = indexFor(hash, table.length);
	Entry<K,V> prev = table[i];
	Entry<K,V> e = prev;

	while (e != null) {
		Entry<K,V> next = e.next;
		Object k;
		if (e.hash == hash &&
			((k = e.key) == key || (key != null && key.equals(k)))) {
			modCount++;
			size--;
			if (prev == e) //如果刪除的是table中的第一項的引用
				table[i] = next;//直接將第一項中的next的引用存入table[i]中
			else
				prev.next = next; //否則將table[i]中當前Entry的前一個Entry中的next置為當前Entry的next
			e.recordRemoval(this);
			return e;
		}
		prev = e;
		e = next;
	}

	return e;
}

//根據Entry來刪除HashMap中的值
final Entry<K,V> removeMapping(Object o) {
	if (size == 0 || !(o instanceof Map.Entry))
		return null;

	Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
	Object key = entry.getKey();//第一步也是先獲得該Entry中儲存的key
	int hash = (key == null) ? 0 : hash(key);//接下來就和上面根據key刪除Entry道理一樣了
	int i = indexFor(hash, table.length);
	Entry<K,V> prev = table[i];
	Entry<K,V> e = prev;

	while (e != null) {
		Entry<K,V> next = e.next;
		if (e.hash == hash && e.equals(entry)) {
			modCount++;
			size--;
			if (prev == e)
				table[i] = next;
			else
				prev.next = next;
			e.recordRemoval(this);
			return e;
		}
		prev = e;
		e = next;
	}

	return e;
}

//清空HashMap中所有的Entry
public void clear() {
	modCount++;
	Arrays.fill(table, null);//將table中儲存的Entry全部置為null
	size = 0;//size置為0
}

//判斷HashMap中是否有key對映到指定的value
public boolean containsValue(Object value) {
	if (value == null)//如果value為空,呼叫下面特定的containsNullValue()方法
		return containsNullValue();

	Entry[] tab = table;//否則遍歷連結串列中的每個Entry
	for (int i = 0; i < tab.length ; i++)
		for (Entry e = tab[i] ; e != null ; e = e.next)
			if (value.equals(e.value))//如果有Entry中的value與指定的value相等
				return true;//返回true
	return false;
}

//value為空時呼叫的方法
private boolean containsNullValue() {
	Entry[] tab = table;
	for (int i = 0; i < tab.length ; i++)
		for (Entry e = tab[i] ; e != null ; e = e.next)
			if (e.value == null)//與上面的不為空時大同小異
				return true;
	return false;
}

//克隆HashMap例項,這裡是淺複製,並沒有複製鍵和值的本身
public Object clone() {
	HashMap<K,V> result = null;
	try {
		result = (HashMap<K,V>)super.clone();
	} catch (CloneNotSupportedException e) {
		// assert false;
	}
	if (result.table != EMPTY_TABLE) {
		result.inflateTable(Math.min(
			(int) Math.min(
				size * Math.min(1 / loadFactor, 4.0f),
				// we have limits...
				HashMap.MAXIMUM_CAPACITY),
		   table.length));
	}
	result.entrySet = null;
	result.modCount = 0;
	result.size = 0;
	result.init();
	result.putAllForCreate(this);

	return result;
}
/********************** 下面跟Iterator有關了 ************************/
//hashIterator實現了Iterator介面
private abstract class HashIterator<E> implements Iterator<E> {
	Entry<K,V> next;        // 下一個Entry
	int expectedModCount;   // 用於fail-fast機制
	int index;              // 當前索引
	Entry<K,V> current;     //當前的Entry

	HashIterator() {
		expectedModCount = modCount;//儲存modCount用於fail-fast機制
		if (size > 0) { // advance to first entry
			Entry[] t = table;
			while (index < t.length && (next = t[index++]) == null)
				;
		}
	}
	//判斷有沒有下一個Entry
	public final boolean hasNext() {
		return next != null;
	}
	//獲得下一個Entry
	final Entry<K,V> nextEntry() {
		if (modCount != expectedModCount)//在迭代的過程中發現被修改了,就會丟擲異常
			throw new ConcurrentModificationException();//即fail-fast
		Entry<K,V> e = next;
		if (e == null) //沒有就丟擲異常
			throw new NoSuchElementException();

		if ((next = e.next) == null) {
			Entry[] t = table;
			while (index < t.length && (next = t[index++]) == null)
				;
		}
		current = e;
		return e;
	}

	public void remove() {//刪除
		if (current == null)
			throw new IllegalStateException();
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		Object k = current.key;
		current = null;
		HashMap.this.removeEntryForKey(k);
		expectedModCount = modCount;
	}
}
//內部class ValueIterator迭代器,它修改了next方法
private final class ValueIterator extends HashIterator<V> {
	public V next() {
		return nextEntry().value;
	}
}
//內部class KeyIterator迭代器,它修改了next方法
private final class KeyIterator extends HashIterator<K> {
	public K next() {
		return nextEntry().getKey();
	}
}
//內部class EntryIterator迭代器,它修改了next方法
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
	public Map.Entry<K,V> next() {
		return nextEntry();
	}
}

//定義上面三個對應的Iterator方法
Iterator<K> newKeyIterator()   {
	return new KeyIterator();
}
Iterator<V> newValueIterator()   {
	return new ValueIterator();
}
Iterator<Map.Entry<K,V>> newEntryIterator()   {
	return new EntryIterator();
}

private transient Set<Map.Entry<K,V>> entrySet = null;

/** 
 * 返回此對映中所包含的鍵的 Set 檢視。 
 * 該 set 受對映的支援,所以對對映的更改將反映在該 set 中, 
 * 反之亦然。如果在對 set 進行迭代的同時修改了對映(通過迭代器自己的 remove 操作除外), 
 * 則迭代結果是不確定的。該 set 支援元素的移除,通過  
 * Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作 
 * 可從該對映中移除相應的對映關係。它不支援 add 或 addAll 操作。 
 */ 
public Set<K> keySet() {
	Set<K> ks = keySet;
	return (ks != null ? ks : (keySet = new KeySet()));
}

private final class KeySet extends AbstractSet<K> {
	public Iterator<K> iterator() {
		return newKeyIterator();
	}
	public int size() {
		return size;
	}
	public boolean contains(Object o) {
		return containsKey(o);
	}
	public boolean remove(Object o) {
		return HashMap.this.removeEntryForKey(o) != null;
	}
	public void clear() {
		HashMap.this.clear();
	}
}

/** 
 * 返回此對映所包含的值的 Collection 檢視。 
 * 該 collection 受對映的支援,所以對對映的更改將反映在該 collection 中, 
 * 反之亦然。如果在對 collection 進行迭代的同時修改了對映(通過迭代器自己的 remove 操作除外), 
 * 則迭代結果是不確定的。該 collection 支援元素的移除, 
 * 通過 Iterator.remove、Collection.remove、removeAll、retainAll 和 clear 操作 
 * 可從該對映中移除相應的對映關係。它不支援 add 或 addAll 操作。 
 */  
public Collection<V> values() {
	Collection<V> vs = values;
	return (vs != null ? vs : (values = new Values()));
}

private final class Values extends AbstractCollection<V> {
	public Iterator<V> iterator() {
		return newValueIterator();
	}
	public int size() {
		return size;
	}
	public boolean contains(Object o) {
		return containsValue(o);
	}
	public void clear() {
		HashMap.this.clear();
	}
}

/** 
 * 返回此對映所包含的對映關係的 Set 檢視。  
 * 該 set 受對映支援,所以對對映的更改將反映在此 set 中, 
 * 反之亦然。如果在對 set 進行迭代的同時修改了對映 
 * (通過迭代器自己的 remove 操作,或者通過在該迭代器返回的對映項上執行 setValue 操作除外), 
 * 則迭代結果是不確定的。該 set 支援元素的移除, 
 * 通過 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作 
 * 可從該對映中移除相應的對映關係。它不支援 add 或 addAll 操作。 
 */  
public Set<Map.Entry<K,V>> entrySet() {
	return entrySet0();
}

private Set<Map.Entry<K,V>> entrySet0() {
	Set<Map.Entry<K,V>> es = entrySet;
	return es != null ? es : (entrySet = new EntrySet());
}

private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
	public Iterator<Map.Entry<K,V>> iterator() {
		return newEntryIterator();
	}
	public boolean contains(Object o) {
		if (!(o instanceof Map.Entry))
			return false;
		Map.Entry<K,V> e = (Map.Entry<K,V>) o;
		Entry<K,V> candidate = getEntry(e.getKey());
		return candidate != null && candidate.equals(e);
	}
	public boolean remove(Object o) {
		return removeMapping(o) != null;
	}
	public int size() {
		return size;
	}
	public void clear() {
		HashMap.this.clear();
	}
}

/************************** 序列化 *****************************/
private void writeObject(java.io.ObjectOutputStream s)
	throws IOException
{
	// Write out the threshold, loadfactor, and any hidden stuff
	s.defaultWriteObject();

	//將table.length寫入流
	if (table==EMPTY_TABLE) {
		s.writeInt(roundUpToPowerOf2(threshold));
	} else {
	   s.writeInt(table.length);
	}

	//將size寫入流
	s.writeInt(size);

	//這裡之所以不直接將table寫出,而是分開寫裡面儲存Entry的key和value的原因是:
	//table陣列定義為了transient,也就是說在進行序列化時,並不包含該成員。
	//為什麼將其設定為transient呢?因為Object.hashCode方法對於一個類的兩個例項返回的是不同的雜湊值。
	//即我們在機器A上算出物件A的雜湊值與索引,然後把它插入到HashMap中,然後把該HashMap序列化後,
	//在機器B上重新算物件的雜湊值與索引,這與機器A上算出的是不一樣的,
	//所以我們在機器B上get物件A時,會得到錯誤的結果。
	//所以我們分開序列化key和value
	if (size > 0) {
		for(Map.Entry<K,V> e : entrySet0()) {
			s.writeObject(e.getKey());
			s.writeObject(e.getValue());
		}
	}
}

private static final long serialVersionUID = 362498820763181265L;

private void readObject(java.io.ObjectInputStream s)
	 throws IOException, ClassNotFoundException
{
	// Read in the threshold (ignored), loadfactor, and any hidden stuff
	s.defaultReadObject();
	if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
		throw new InvalidObjectException("Illegal load factor: " +
										   loadFactor);
	}

	// set other fields that need values
	table = (Entry<K,V>[]) EMPTY_TABLE;

	// Read in number of buckets
	s.readInt(); // ignored.

	// Read number of mappings
	int mappings = s.readInt();
	if (mappings < 0)
		throw new InvalidObjectException("Illegal mappings count: " +
										   mappings);

	// capacity chosen by number of mappings and desired load (if >= 0.25)
	int capacity = (int) Math.min(
				mappings * Math.min(1 / loadFactor, 4.0f),
				// we have limits...
				HashMap.MAXIMUM_CAPACITY);

	// allocate the bucket array;
	if (mappings > 0) {
		inflateTable(capacity);
	} else {
		threshold = capacity;
	}

	init();  // Give subclass a chance to do its thing.

	// Read the keys and values, and put the mappings in the HashMap
	for (int i = 0; i < mappings; i++) {
		K key = (K) s.readObject();
		V value = (V) s.readObject();
		putForCreate(key, value);
	}
}

// These methods are used when serializing HashSets
int   capacity()     { return table.length; }
float loadFactor()   { return loadFactor;   }

3.HashMap 遍歷方式

    HashMap的遍歷方式比較單一,一般分兩步走:

        1. 獲得Entry或key或value的Set集合;

        2. 通過Iterator迭代器遍歷此Set集合。

3.1遍歷HashMap的Entry

//假設map是HashMap物件
//map中的key是String型別,value是Integer型別
String key = null;
Integer integ = null;
Iterator iter = map.entrySet().iterator();//獲得Entry的Iterator
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next(); //先獲取Entry
    //再獲取key和value
    key = (String)entry.getKey();
    integ = (Integer)entry.getValue();
}

3.2遍歷HashMap的key

//假設map是HashMap物件
//map中的key是String型別,value是Integer型別
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator(); //獲得Key的Iterator
while (iter.hasNext()) {
    //直接獲取key
    key = (String)iter.next();
    //根據key,呼叫get方法獲取value
    integ = (Integer)map.get(key);
}

3.3遍歷HashMap的value

//假設map是HashMap物件
//map中的key是String型別,value是Integer型別
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();//獲得value的Iterator
while (iter.hasNext()) {
    value = (Integer)iter.next(); //直接獲取
}

    HashMap的遍歷比較簡單,主要是通過Iterator迭代器來實現,但是方式有三種,都可以最終獲得value值。

    HashMap就討論到這吧,如有錯誤,歡迎留言指正~

_____________________________________________________________________________________________________________________________________________________

-----樂於分享,共同進步!