1. 程式人生 > >java原始碼分析之集合框架HashMap 10

java原始碼分析之集合框架HashMap 10


HashMap

  1. HashMap 是一個散列表,它儲存的內容是鍵值對(key-value)對映。
  2. HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable介面。
  3. HashMap 的實現不是同步的,這意味著它不是執行緒安全的。它的key、value都可以為null。此外,HashMap中的對映不是有序的。
  4. HashMap 的例項有兩個引數影響其效能:“初始容量” 和 “載入因子”。容量 是雜湊表中桶的數量,初始容量 只是雜湊表在建立時的容量。載入因子 是雜湊表在其容量自動增加之前可以達到多滿的一種尺度。當雜湊表中的條目數超出了載入因子與當前容量的乘積時,則要對該雜湊表進行 rehash 操作(即重建內部資料結構),從而雜湊表將具有大約兩倍的桶數。
  5. 通常,預設載入因子是 0.75, 這是在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設定初始容量時應該考慮到對映中所需的條目數及其載入因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以載入因子,則不會發生 rehash 操作。

          注意,此實現不是同步的。如果多個執行緒同時訪問一個雜湊對映,而其中至少一個執行緒從結構上修改了該對映,則它必須

保持外部同步。(結構上的修改是指新增或刪除一個或多個對映關係的任何操作;僅改變與例項已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該對映的物件進行同步操作來完成。如果不存在這樣的物件,則應該使用Collections.synchronizedMap 方法來“包裝”該對映。最好在建立時完成這一操作,以防止對對映進行意外的非同步訪問,如下所示:

Map m = Collections.synchronizedMap(new HashMap(...))


hashMap儲存結構:



Entry實體:

      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) {  
    }  
}  




HashMap 的API:



HashMap的繼承關係:



public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable介面



屬性:

	// 預設的初始容量是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陣列,長度是2的冪。
    // HashMap是採用拉鍊法實現的,每一個Entry本質上是一個單向連結串列
    transient Entry[] table;
    
    // HashMap的大小,它是HashMap儲存的鍵值對的數量
    transient int size;
    
    // HashMap的閾值,用於判斷是否需要調
    //整HashMap的容量(threshold = 容量*載入因子)
    int threshold;
    
    // 載入因子實際大小
    final float loadFactor;
    
    // HashMap被改變的次數
    transient volatile int modCount;
    
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALU

	//hash種子
	transient int hashSeed = 0;



 
 
 
 
 
        主要來看看loadFactor屬性,loadFactor表示Hash表中元素的填滿程度。
  
 

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

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

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

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

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

建構函式:

//initialCapacity初始容量,loadFactor載入因子,指定“容量大小”和“載入因子”的建構函式
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //Float.isNaN()判斷loadFactor是否是非數字值
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

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

   
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    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); //擴充套件table  

        // 將m中的全部元素逐個新增到HashMap中
        putAllForCreate(m);
    }
在構造HashMap的時候,如果我們指定了載入因子和初始容量的話就呼叫第一個構造方法,否則就用預設的。預設的初始容量為16,載入因子為0.75。建構函式做了什麼,儲存載入因子,並將初始容量先賦給闋值,但是這裡並不是真正的闋值,正真的闋值是在第一次put時,初始化的,即闋值=載入因子*容量


HashMap 方法:


put(K key, V value) 

put(K key,V value)
          在此對映中關聯指定值與指定鍵。

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) {
        //獲取和toSize最接近的2的冪作為容量  
        int capacity = roundUpToPowerOf2(toSize);

        //重新計算閾值 threshold = 容量 * 載入因子  
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        
        //用該容量初始化table,建立一個定長陣列table,table裡儲存的型別是Entry
	table = new Entry[capacity];

	//初始化HashSeed的值  
        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;
    }

     擴充套件: 

一、Integer.highestOneBit()返回具有至多單個 1 位的 int 值,在指定的 int 值中最高位(最左邊)的 1 位的位置。如果指定的值在其二進位制補碼錶示形式中不具有 1 位,即它等於零,則返回零。 
        比如 17, 二進位制是
        17 0000,0000,0000,0000,0000,0000,0001,0001
        它返回的是最高位的1個1, 其它全是0 
        16 0000,0000,0000,0000,0000,0000,0001,0000
    總之一句話:返回最高位為1, 其它位為0的數。

二、用指定初始容量和指定載入因子構造一個新的空雜湊表。其中initHashSeedAsNeeded方法用於初始化hashSeed引數,其中hashSeed用於計算key的hash值,它與key的hashCode進行按位異或運算。這個hashSeed是一個與例項相關的隨機值,主要用於解決hash衝突。


      在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) {  
    //遍歷一遍table陣列
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
        if (e.key == null) {   
            V oldValue = e.value;  
            e.value = value;  
            
            /*對已經在HashMap中k位置進行v的覆蓋時,會呼叫此方法,空函式 */
            e.recordAccess(this);  
            
            return oldValue;  
        }  
    }  
    
    //如果table為null  或者原table表裡沒有key==null的
    modCount++;  
    addEntry(0, null, value, 0);//如果鍵為null的話,則hash值為0  
    return null;  
}  
     從方法中可以看出首先會定位到table[0]處,然後遍歷table陣列,依次查詢是否有key==null的鍵,如果有,將對應的value用新的value值取代,同時返回舊的value值。除非table為空或者原來table裡面沒有key==null項,才會走下面的程式碼addEntry()。那麼呼叫 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的索引位置。插入之前先判斷容量是否足夠並且table[bucketIndex] !=null,HashMap中是2倍擴容。resize() ,方法如下:

    //用新的容量來給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迴圈  
            }  
        }  
    }  

若table容量夠,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沒有充分利用
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    //這個方法有點意思,也是為什麼容量要設定為2的冪的原因  
    static int indexFor(int h, int length) {
        
        return h & (length-1);//h&(length-1)===h%length
    }

h & (length-1)等於h % length

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

具體解釋請看程式碼:

public class Test {
	
	public static void main(String[] args) {
	
		System.out.println("^:" + (3^3) );
		System.out.println("---------------------------------");
		System.out.println("23%4:" + (23%4));
		System.out.println("23&(4-1):" + (23&(4-1)));
		System.out.println("23%3:" + (23%3));
		System.out.println("23&(3-1):" + (23&(3-1)));
		System.out.println("---------------------------------");
		System.out.println("26%4:" + (26%4));
		System.out.println("26&(4-1):" + (26&(4-1)));
		System.out.println("26%3:" + (26%3));
		System.out.println("26&(3-1):" + (26&(3-1)));
		
		
		System.out.println("i&偶數:----------------------------------------》");
		for(int i=10; i<30; i++){
			System.out.println(i+"&2:" + (i&2));
			System.out.println(i+"&2:" + (68&2));
		}
		System.out.println("i&奇數:----------------------------------------》");
		for(int i=10; i<30; i++){
			System.out.println(i+"&3:" + (i&3));
			System.out.println(i+"&3:" + (68&3));
		}
		
		
	}

}

輸出結果:

^:0
---------------------------------
23%4:3
23&(4-1):3
23%3:2
23&(3-1):2
---------------------------------
26%4:2
26&(4-1):2
26%3:2
26&(3-1):2
i&偶數:----------------------------------------》
10&2:2
10&2:0
11&2:2
11&2:0
12&2:0
12&2:0
13&2:0
13&2:0
14&2:2
14&2:0
15&2:2
15&2:0
16&2:0
16&2:0
17&2:0
17&2:0
18&2:2
18&2:0
19&2:2
19&2:0
20&2:0
20&2:0
21&2:0
21&2:0
22&2:2
22&2:0
23&2:2
23&2:0
24&2:0
24&2:0
25&2:0
25&2:0
26&2:2
26&2:0
27&2:2
27&2:0
28&2:0
28&2:0
29&2:0
29&2:0
i&奇數:----------------------------------------》
10&3:2
10&3:0
11&3:3
11&3:0
12&3:0
12&3:0
13&3:1
13&3:0
14&3:2
14&3:0
15&3:3
15&3:0
16&3:0
16&3:0
17&3:1
17&3:0
18&3:2
18&3:0
19&3:3
19&3:0
20&3:0
20&3:0
21&3:1
21&3:0
22&3:2
22&3:0
23&3:3
23&3:0
24&3:0
24&3:0
25&3:1
25&3:0
26&3:2
26&3:0
27&3:3
27&3:0
28&3:0
28&3:0
29&3:1
29&3:0



       再回到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。



get(Object key)

get(Object key)
          返回指定鍵所對映的值;如果對於該鍵來說,此對映不包含任何對映關係,則返回 null

    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        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;
        }
        return null;
    }
    
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
        從原始碼中更可以看出,從HashMap中get元素時,先計算key的hashCode,找到陣列中國對應的位置,然後通過key的equals在對應位置的連結串列中找到需要的元素。


其他方法:

//返回當前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方法在上面已經拿出來分析了  
}  
  
//根據已有的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());  
}  

//看看需不需要建立新的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,
        //將新的value替換舊的value值退出  
        if (e.hash == hash &&  
            ((k = e.key) == key || (key != null && key.equals(k)))) {  
            e.value = value;  
            return;  
        }  
    }  
  
    createEntry(hash, key, value, i);//否則需要建立新的Entry  
}  
  

  
//用新的容量來給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 = next; //設定e為下一個Entry,繼續上面while迴圈  
        }  
    }  
}  
  
//將指定的Map中所有映射覆制到現有的HashMap中,這些對映關係將覆蓋當前HashMap
//中針對指定鍵相同的對映關係  
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) {  
        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進去,如果有相同的key則將value替換,否則建立新的Entry對映 
        put(e.getKey(), e.getValue()); 
}  
  
//根據指定的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  
            //否則將table[i]中當前Entry的前一個Entry中的next置為當前Entry的next
                prev.next = 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++;  
    //將指定的 null 值分配給指定 Entry 型陣列的每個元素
    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;//否則遍歷陣列表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為空時呼叫的方法  ,
//這裡查詢value要遍歷兩遍,是因為hashmap儲存結構是陣列加連結串列
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方法:

int expectedModCount;   // 用於fail-fast機制 看 http://blog.csdn.net/wangnanwlw/article/details/52293134

//抽象類hashIterator重寫了Iterator介面的(hasnext(),remove()) 
//抽象類hashIterator 沒有重寫next()
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機制  
        //此處為next賦值,next為陣列table中第一個不為空的值
        if (size > 0) {  
            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();  
        //查詢下一個不為空的Entry,防止本該陣列索引對應的連結串列已經到鏈尾
        //index的值是緊接著HashIterator() 中的index的值,繼續++
        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;  
  
/**  
 * keySet()返回此對映中所包含的鍵的 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  
{  
    //將當前類的非靜態和非瞬態(transient)欄位寫入此流。此欄位只能從正在序
    //列化的類的 writeObject 方法中呼叫。如果從其他地方呼叫該欄位,
    //則將丟擲 NotActiveException。 
    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;   } 


hashMap 遍歷:

package wn.comeOn.java.test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class Test2 {

	public static void main(String[] args) {

		HashMap<Integer, Character> map = new HashMap<Integer, Character>();

		Character j = 'a';
		for (int i = 0; i < 10; i++, j++) {
			map.put(i, j);
		}

		// 遍歷Entry
		System.out.println("遍歷Entry:");
		Integer key = null;
		Character value = null;
		Iterator itr01 = map.entrySet().iterator();
		while (itr01.hasNext()) {
			Map.Entry entry = (Entry) itr01.next();
			key = (Integer) entry.getKey();
			value = (Character) entry.getValue();
			System.out.println(key + "--->" + value);
		}

		// 遍歷key
		System.out.println("遍歷key:");
		Integer key02 = null;
		Iterator itr02 = map.keySet().iterator();
		while (itr02.hasNext()) {
			key = (Integer) itr02.next();
			System.out.println(key );
		}

		// 遍歷value
		System.out.println("遍歷value:");
		Character value03 = null;
		Iterator itr03 = map.values().iterator();
		while (itr03.hasNext()) {
			value = (Character) itr03.next();
			System.out.println( value);
		}

	}

}
輸出結果:

遍歷Entry:
0--->a
1--->b
2--->c
3--->d
4--->e
5--->f
6--->g
7--->h
8--->i
9--->j
遍歷key:
0
1
2
3
4
5
6
7
8
9
遍歷value:
a
b
c
d
e
f
g
h
i
j



    最後給大家分享一個連結:https://www.cs.usfca.edu/~galles/visualization/OpenHash.html 。可以很直觀的看到HashMap新增和刪除資料的過程。幫助我們更好的理解HashMap。