手撕 HashMap(空口無憑,實戰爲真)

如果你還不瞭解 HashMap,建議你先看我上一篇博客:
分享 HashMap 的精髓,它永遠比你自己的寫 map 效率高

這篇博客更多的闡述瞭如何去寫,更多的是對代碼的分析,需要有閱讀代碼的能力。
如果你不太擅長閱讀代碼,可以去看我的上一篇博客,裏面更多的是對知識點語言描述,相對會更容易理解。

鍵值對的存儲方式:Node

首先開始構建HashMap,並添加靜態內部類Node
然後給Node創建屬性 key, value, next
然後實現Node的基本方法
方法比較簡單,只添加少量註釋,粗略掃一遍即可

public class MyHashMap<K, V> extends AbstractMap<K, V> {
    static class Node<K, V> implements Map.Entry<K, V> {
    	final int hash; // 記錄key的hashCode()值
        final K key;    // 鍵
        V value;        // 值
        Node<k, V> next;      // 指向下一個鏈表結點
        // 構造方法 給屬性賦值
        Node(int hash, K key, V value) {
        	this.hash = hash;
            this.key = key;
            this.value = value;
        }
        // get 方法
        public final int getHash()       { return hash; }
        public final K getKey()          { return key; }
        public final V getValue()        { return value; }
        // set方法 設置新value 返回舊value
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        public final String toString() { return key + "=" + value; }
        public final int hashCode() {
            return key.hashCode() ^ value.hashCode();
        }
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (e.hashCode() == this.hashCode())
                    return true;
            }
            return false;
        }
	}
}

屬性:static和private成員變量

首先介紹 final static 變量

// 默認的數組大小 16
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 數組最大的大小 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認的負載因子爲 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

屬於對象的成員變量

private Node<K, V>[] table; // 存放鍵值對的數組
private int size;           // 存放鍵值對的數目
private int threshold;      // 擴容閾值(表示size達到多少要擴容)
private int modCount;       // 相當於版本號
private final float loadFactor; // 負載因子(不可變)

構造方法(避免佔用資源,延遲初始化)

  • 這裏的代碼與 jdk 源碼大致相同,僅檢查參數合理性,然後爲屬性賦值。
  • 我們發現,在給 threshold 賦值時,沒有直接使用傳遞的 initialCapacity 參數,而是用了tableSizeFor() 方法計算出不小於它的最小的2的次方數作爲它的值
  • 在 HashMap 剛剛創建時,內部的數組並沒有初始化,這樣子只有到真正用的時候才初始化,節省資源。
public MyHashMap(int initialCapacity, float loadFactor) {
    // 如果容量小於0,拋出異常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 容量大於最大值,則爲最大值
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 負載因子不能小於0 否則拋出異常
    if (loadFactor <= 0)
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    // 賦值操作
    this.loadFactor = loadFactor;
    // tableSizeFor() 計算容量並賦值
    this.threshold = tableSizeFor(initialCapacity);
}

另兩個構造方法十分簡單,明白主構造函數即可

public MyHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public MyHashMap() {
	this.threshold = DEFAULT_INITIAL_CAPACITY;
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

tableSizeFor(),對數組長度的高效計算

  • 此處代碼與 jdk 源碼相同
  • 爲了保證效率,此處計算全部採用位運算
  • a >>> b 表示a的二進制數中,所有位的數字,全部右移b位
  • a | b 是或運算,a與b表示的二進制數字中的所有相同的數字,如果都爲0,則計算出的該位爲0,否則該位爲1。
  • 先跳過第一行,我們看後面幾行,先進行右移運算,然後再或運算。
  • 我們發現,本來正數的最高位肯定不爲0,所以在二進制中一定爲1,第一次右移之後,原來的第一位就變成第二位,所以右移後的數字在第二位爲1,,進行異或操作,第一位有1,計算出來的結果爲1,第二位有1,計算出來也爲1。所以第一次運算過後保證二進制數據的前兩位一定爲1。
  • 同理,第二次計算過後,前4位一定爲1,然後8位,16位,32位,因爲Integer一共只有32位,所以可以保證從第一位到最後一位都爲1。
  • 然後這時只要加上1,就是2的倍數。
  • 現在很容易明白,前面減去1是爲了防止這個數本身已經是2的倍數,通過計算後會變成它的兩倍。
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

hash()方法,提高離散程度的高效算法

  • 這段代碼僅僅是對 jdk 源碼中的代碼寫得更清晰一點,效果與源碼是相同的。
  • 此方法是對 key 的 hashCode() 值進行二次計算,將原值與它的無符號右移16位進行異或計算。
  • 因爲我們平時使用到的 HashMap 用到的容量很小,但是 hashCode() 值很大,我們用哈希函數取模計算位置時,僅僅只使用到了最後的那幾位。
  • 而這個方法對 hashCode() 值的前後16位進行異或操作,使得後16位的哈希值也同時包含了前16位的特性,這樣的函數可以充分利用整個 hashCode() 值的更多位上的數值,擁有更好的離散性,因此可以更好避免衝突,從而使得 HashMap 擁有更好的性能。
static final int hash(Object key) {
    if(key == null)
        return 0;
    int h = key.hashCode();
    return h ^ (h >>> 16);
}

get()方法,三大主方法之一

get() 方法用於獲取 key 對應的 value,要獲取到 value 首先要獲取到鍵值對結點對象。如果獲取到,則返回 value,否則返回null

public V get(Object key) {
    Node<K, V> e = getNode(key);
    return e == null ? null : e.value;
}

所以主要的精力放在寫 getNode() 方法上

  • 二次哈希
  • 計算出結點的位置
  • 然後遍歷數組該位置的桶中所有結點
  • 如果存在結點,則返回,否則遍歷結束後返回 null

計算位置是get,put,remove都需要用到的方法,所以把它單獨寫出,可以提高公用代碼量。雖然只有一行,但是更便於表示它的步驟,代碼更易閱讀。

static final int getIndex(int hash, int n) {
    return hash & (n - 1);
}

getNode() 方法

private Node<K, V> getNode(Object key) {
    // hash()
    int h = hash(key);
    Node<K, V>[] tab = table;
    // 計算位置
    int n = tab.length;
    int i = getIndex(h, n);
    // 遍歷該數組該位置桶中的所有結點
    Node<K, V> p = tab[i];
    if(key == null) {
        while(p != null) {
            if(key == p.getKey())
                return p; // 找到
            p = p.next;
        }
    }
    else {
        while(p != null) {
            if(key.equals(p.getKey()))
                return p; // 找到
            p = p.next;
        }
    }
    // 遍歷所有沒有找到
    return null;
}

resize()方法,學會put()方法的前提

  • 擴容方法,一般按照2倍的方式進行擴容。
  • 在數組還未初始化時,和存入鍵值對數目大於 threshold 時調用
  1. 計算新的數組容量和可容納鍵值對數目 newCap newThr
  2. 根據新的容量創建新的數組
  3. 將舊數組中的內容全部添加入新數組

該方法比源碼簡略,且不涉及紅黑樹操作,更便於理解

private final Node<K, V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr;
    // 計算newCap,newThr
    if (oldCap > 0) { // 按照兩倍進行擴容
        newCap = oldCap << 1;
        newThr = oldThr << 1;
        // 如果容量較小,則threshold不精確,重新計算
        if(newCap <= 64) {
            newThr = (int) (newCap * loadFactor);
        }
        // 如果到最大容量,則把擴容閾值設爲最大
        if(newCap == MAXIMUM_CAPACITY)
            newThr = Integer.MAX_VALUE;
    }
    else { // oldCap爲0,說明數組還未被初始化
        newCap = oldThr;
        int ft = (int) (newCap * loadFactor);
        newThr = ft < MAXIMUM_CAPACITY ? ft : Integer.MAX_VALUE;
    }
    // 創建新數組
    @SuppressWarnings("unchecked")
    Node<K,V>[] newTab = (Node<K, V>[]) new Node<?,?>[newCap];
    // 將舊數組結點添加入新數組
    if(oldCap != 0) {
        for(int t = 0; t < oldCap; t++) {
            Node<K, V> p = oldTab[t];
            while(p != null) {
                int h = p.getHash();
                int i = getIndex(h, newCap);
                Node<K, V> next = p.next;
                p.next = newTab[i];
                newTab[i] = p;
                p = next;
            }
        }
    }
    // 更新成員變量
    threshold = newThr;
    table = newTab;
    return newTab;
}

put()方法,三大主方法之一

  1. 如果存在 key,則更新它對應的 value
  2. 如果不存在,則插入新的鍵值對
  3. 如果數組未初始化,先初始化
  4. 二次哈希值
  5. 計算數組中桶的位置
  6. 遍歷桶中結點,如果存在,則更新;否則在尾部插入新節點。
  7. 更新 modCount
  8. 更新 size
  9. 如果 size 大於 threshold,則需要擴容
public V put(K key, V value) {
    Node<K, V>[] tab = table;
    V oldV = null;
    // 1、如果未初始化則初始化
    if(tab == null || tab.length == 0)
        tab = resize();
    // 2、二次hash
    int n = tab.length;
    int h = hash(key);
    // 3、計算位置
    int i = getIndex(h, n);
    // 4、遍歷結點,更新或插入
    boolean updated = false; // 表示是否更新過結點
    Node<K, V> p = tab[i];   // 首節點
    if(p == null) {
        tab[i] = new Node<>(h, key, value);
    }
    else {
        if(key == null) {
            if(key == p.getKey()) { // 首節點
                oldV = p.setValue(value);
                updated = true;
            }
            if(! updated) { // 首節點未更新,遍歷之後結點
                while(p.next != null) {
                    if(key == p.next.getKey()) {
                        oldV = p.next.setValue(value);
                        updated = true;
                        break;
                    }
                    p = p.next;
                }
            }
            if(! updated) { // 沒有更新過,插入新節點
                Node<K, V> node = new Node<>(h, key, value);
                p.next = node;
            }
        }
        else { // key不是null的情況
            if(key.equals(p.getKey())) { // 首節點
                oldV = p.setValue(value);
                updated = true;
            }
            if(! updated) { // 首節點未更新,遍歷之後結點
                while(p.next != null) {
                    if(key.equals(p.next.getKey())) {
                        oldV = p.next.setValue(value);
                        updated = true;
                        break;
                    }
                    p = p.next;
                }
            }
            if(! updated) { // 沒有更新過,插入新節點
                Node<K, V> node = new Node<>(h, key, value);
                p.next = node;
            }
        }
    }
    // 5、更新modCount
    ++modCount;
    // 6、更新size
    if(! updated) // 沒有更新,說明新增結點
        ++size;
    // 7、檢查擴容
    if(size > threshold)
        resize();
    return oldV;
}

remove()方法,三大主方法之一

移除 HashMap 中的 key 對應的結點,返回它的 value 值,否則返回 null。

  1. 二次哈希
  2. 計算出在數組中對應的桶的位置
  3. 遍歷桶中結點
public V remove(Object key) {
    Node<K, V>[] tab = table;
    // 1、二次哈希
    int h = hash(key);
    // 2、計算位置
    int n = tab.length;
    int i = getIndex(h, n);
    // 3、遍歷桶中結點
    Node<K, V> p = tab[i];
    if(p == null) // 桶爲空
        return null;
    if(key == null) {
        // 首節點
        if(key == p.getKey()) {
            V value = p.getValue();
            tab[i] = p.next;
            return value;
        }
        // 之後的結點
        while(p.next != null) {
            if(key == p.next.getKey()) {
                V value = p.next.getValue();
                p.next = p.next.next;
                return value;
            }
        }
    }
    else {
        // 首節點
        if(key.equals(p.getKey())) {
            V value = p.getValue();
            tab[i] = p.next;
            return value;
        }
        // 之後的結點
        while(p.next != null) {
            if(key.equals(p.next.getKey())) {
                V value = p.next.getValue();
                p.next = p.next.next;
                return value;
            }
        }
    }
    return null;
}

entrySet():迭代HashMap的方法

  • 首先我們要知道,Map 接口是不具有 Iterator() 方法的,因此自身並不具備迭代遍歷的能力。
  • 因此,爲了遍歷 Map 中的元素,則必須對 Map 中的元素做一個 Collection 集合進行迭代方可。
  • Map 共有三個返回集合的方法,一個是 keySet() 方法,用於返回所有 key 組成的集合,還有一個是 values() 方法,用於返回所有的 value 集合,並且可以重複,最後是 entrySet() 方法,返回所有鍵值對的集合。
  • 而 entrySet() 是前兩個方法的基礎,因爲只需要迭代所有的鍵值對,就可以迭代所有的 key 和 value。
  • 對於該 Map 中返回的集合,只是對於 Map 起一個迭代功能的作用,裏面的對象都是原有的同一個對象,對集合對象的增刪改查也會直接影響到原有的 Map。
  1. 首先需要有一個 EntrySet 類
  2. 既然是作爲迭代,那必然先有 EntryIterator 類
  3. 主要完成 EntryIterator 中的關鍵方法
  4. EntrySet 中的其他方法大家按需寫即可
  5. 調用方法時,如果 EntrySet 已經有了對象,則直接返回,如果沒有,則新建一個對象返回
// 創建一個變量保存entrySet
private Set<Map.Entry<K,V>> entrySet;
// entrySet()方法
public Set<Entry<K, V>> entrySet() {
    // 沒有則創建一個
    if(entrySet == null) {
        entrySet = new EntrySet();
    }
    return entrySet; // 返回
}

EntryIterator 迭代器類

class EntryIterator implements Iterator<Entry<K, V>> {
    Node<K, V> next;        // 下一個結點
    Node<K, V> current;     // 當前結點
    // 快速失敗機制,如果和modCount不一樣,則拋異常
    int expectedModCount;
    int index;              // 迭代到哪一個桶
    EntryIterator() { // 構造方法
        expectedModCount = modCount;
        current = next = null;
        // 找出第一個結點 
        index = 0;
        while((next = table[index]) == null) {
            index++;
        }
    }
    public Entry<K, V> next() {
        // 如果modCount與原先不同,說明已被修改,拋出異常
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        // 如果next爲null,說明已經迭代到末尾,拋出異常
        Node<K, V> e = next;
        if (e == null)
            throw new NoSuchElementException();
        // 找出下一個結點
        if ((next = (current = e).next) == null)
            while(++index < table.length && (next = table[index]) == null) {}
        return e;
    }
    public final void remove() {
        Node<K,V> p = current;
        // 當前爲null,說明已經remove過,無法再remove
        if (p == null)
            throw new IllegalStateException();
        // 如果modCount與原先不同,說明已被修改,拋出異常
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        // 調用MyHashMap的remove方法來移除
        MyHashMap.this.remove(key);
        expectedModCount = modCount;
    }
    public final boolean hasNext() {
        return next != null;
    }
}

EntrySet 類。如果上面的代碼都已經寫過了,那這裏的代碼就已經很簡單了,註釋相應減少,不作重點。

class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { MyHashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
    public final boolean contains(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>) o;
        Object key = e.getKey();
        Node<K,V> candidate = getNode(key);
        return candidate != null && candidate.equals(e);
    }
    public final boolean remove(Object o) {
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K, V> candidate = getNode(key);
            if(candidate != null && candidate.equals(e)) {
                MyHashMap.this.remove(key);
                return true;
            }
            return false;
        }
        return false;
    }
    public final Spliterator<Map.Entry<K,V>> spliterator() {
        // 此方法不做討論,省略
        return null;
    }
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        if (action == null)
            throw new NullPointerException();
        Iterator<Entry<K, V>> it = iterator();
        while(it.hasNext())
            action.accept(it.next());
    }
}

Map接口

要寫HashMap,先從它的接口Map開始寫起(僅僅一個接口,只包含了需要實現的方法,不涉及知識點,大家看註釋即可理解)。
(當然,爲了方便,我們可以直接 import JDK 自帶的 Map 接口)

public interface Map<K, V> { //Map接口,指定了需要寫哪些方法
    int size();    // 返回存儲的鍵值對個數
    boolean isEmpty(); // 判空
    boolean containsKey(Object key); // 是否包含key
    boolean containsValue(Object value);// 是否包含value
    V get(K key);          // 獲取key對應的value
    V put(K key, V value); // 存儲鍵值對
    V remove(Object key);  // 移除指定key的鍵值對
    // 將Map中的所有鍵值對存儲到該Map中
    void putAll(Map<? extends K, ? extends V> m);
    void clear();    // 清空
    Set<K> keySet(); // 返回key的集合
    Collection<V> values(); // 返回value集合(可重複)
    Set<Map.Entry<K, V>> entrySet(); // 返回鍵值對集合
    // 內部接口 定義了鍵值對的所擁有的方法
    interface Entry<K, V> {
        K getKey();
        V getValue();
        // 設置新value 返回舊value
        V setValue(V value);
    }
}

AbstractMap抽象類(isEmpty,toString,equals,hashCode)

看過 java.util 包裏內容的應該知道,jdk 源碼對於每一個集合接口,都寫了一個抽象類,實現了一些基本的方法,這樣在寫具體的類的時候就不用寫這些基本代碼。

  • toString()
  • equals(Object obj)
  • hashCode()

筆者並未全部列出,此處不作重點,大家可以看 jdk 的詳細源碼

public abstract class AbstractMap<K,V> implements Map<K,V> {
    // 用於表示
	public String toString() {
		StringBuilder str = new StringBuilder();
		Iterator<Entry<K,V>> it = entrySet().iterator();
		while(it.hasNext())
			str.append(it.next().toString()).append(" ");
		return str.toString();
	}
	public boolean equals(Object o) {
		if(o == this)
			return true;
		if(o instanceof Map) {
			Map<?,?> map = (Map<?,?>) o;
			Iterator<?> itm = map.entrySet().iterator();
			Iterator<Entry<K, V>> itt = entrySet().iterator();
			while(itm.hasNext() && itt.hasNext()) {
				Object om = itm.next();
				Entry<K, V> e = itt.next();
				if(om.equals(e) == false)
					return false;
			}
			if(itm.hasNext() || itt.hasNext())
				return false;
		}
		return false;
	}
	public int hashCode() {
		int h = 0;
		Iterator<Entry<K, V>> it = entrySet().iterator();
		if(it.hasNext())
			h = it.next().hashCode();
		while(it.hasNext())
			h = h ^ it.next().hashCode();
		return h;
    }
}

題外話

這篇文章我並沒有直接拿 HashMap 的源代碼來和大家分析。不過事實上筆者的 HashMap 在一般的效用上來說與 JDK 的源碼並沒有太大的差距。
我們要去學習 HashMap 源碼的話,很好的方式就是去親手實現一個。可能一開始寫得並不好,不過隨着你不斷去研究源碼中的這些優點,你可以試着不斷將這些優點囊括到你自己的代碼中來,這樣就會取得長足的進步。
此外,若是你能想到更好的源碼中沒有用到的優秀算法,那你的能力想必也是非常之高了。

MyHashMap 代碼(需要直接複製)

如果有錯誤,也請在評論區指正。

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;

public class MyHashMap<K, V> extends AbstractMap<K, V> {
    static class Node<K, V> implements Map.Entry<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;
        Node(int hash, K key, V value) {
            this.hash = hash;
            this.key = key;
            this.value = value;
        }
        public final int getHash()       { return hash; }
        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;
        }
        public final String toString() { return key + "=" + value; }
        public final int hashCode() {
            return hash(key) ^ hash(value);
        }
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (e.hashCode() == this.hashCode())
                    return true;
            }
            return false;
        }
    }
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int hash(Object key) {
        if(key == null)
            return 0;
        int h = key.hashCode();
        return h ^ (h >>> 16);
    }
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    static final int getIndex(int hash, int n) {
        return hash & (n - 1);
    }
    private Node<K, V>[] table;
    private int size;
    private int threshold;
    private int modCount;
    private final float loadFactor;
    public MyHashMap(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;
        this.threshold = tableSizeFor(initialCapacity);
    }
    public MyHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public MyHashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }
    public V get(Object key) {
        Node<K, V> e = getNode(key);
        return e == null ? null : e.value;
    }
    private Node<K, V> getNode(Object key) {
        // hash()
        int h = hash(key);
        Node<K, V>[] tab = table;
        // 計算位置
        int n = tab.length;
        int i = getIndex(h, n);
        // 遍歷該數組該位置桶中的所有結點
        Node<K, V> p = tab[i];
        if(key == null) {
            while(p != null) {
                if(key == p.getKey())
                    return p; // 找到
                p = p.next;
            }
        }
        else {
            while(p != null) {
                if(key.equals(p.getKey()))
                    return p; // 找到
                p = p.next;
            }
        }
        // 遍歷所有沒有找到
        return null;
    }
    private final Node<K, V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr;
        // 計算newCap,newThr
        if (oldCap > 0) { // 按照兩倍進行擴容
            newCap = oldCap << 1;
            newThr = oldThr << 1;
            // 如果容量較小,則threshold不精確,重新計算
            if(newCap <= 64) {
                newThr = (int) (newCap * loadFactor);
            }
            // 如果到最大容量,則把擴容閾值設爲最大
            if(newCap == MAXIMUM_CAPACITY)
                newThr = Integer.MAX_VALUE;
        }
        else { // oldCap爲0,說明數組還未被初始化
            newCap = oldThr;
            int ft = (int) (newCap * loadFactor);
            newThr = ft < MAXIMUM_CAPACITY ? ft : Integer.MAX_VALUE;
        }
        // 創建新數組
        @SuppressWarnings("unchecked")
        Node<K,V>[] newTab = (Node<K, V>[]) new Node<?,?>[newCap];
        // 將舊數組結點添加入新數組
        if(oldCap != 0) {
            for(int t = 0; t < oldCap; t++) {
                Node<K, V> p = oldTab[t];
                while(p != null) {
                    int h = p.getHash();
                    int i = getIndex(h, newCap);
                    Node<K, V> next = p.next;
                    p.next = newTab[i];
                    newTab[i] = p;
                    p = next;
                }
            }
        }
        // 更新成員變量
        threshold = newThr;
        table = newTab;
        return newTab;
    }
    public V put(K key, V value) {
        Node<K, V>[] tab = table;
        V oldV = null;
        // 1、如果未初始化則初始化
        if(tab == null || tab.length == 0)
            tab = resize();
        // 2、二次hash
        int n = tab.length;
        int h = hash(key);
        // 3、計算位置
        int i = getIndex(h, n);
        // 4、遍歷結點,更新或插入
        boolean updated = false; // 表示是否更新過結點
        Node<K, V> p = tab[i];   // 首節點
        if(p == null) {
            tab[i] = new Node<>(h, key, value);
        }
        else {
            if(key == null) {
                if(key == p.getKey()) { // 首節點
                    oldV = p.setValue(value);
                    updated = true;
                }
                if(! updated) { // 首節點未更新,遍歷之後結點
                    while(p.next != null) {
                        if(key == p.next.getKey()) {
                            oldV = p.next.setValue(value);
                            updated = true;
                            break;
                        }
                        p = p.next;
                    }
                }
                if(! updated) { // 沒有更新過,插入新節點
                    Node<K, V> node = new Node<>(h, key, value);
                    p.next = node;
                }
            }
            else { // key不是null的情況
                if(key.equals(p.getKey())) { // 首節點
                    oldV = p.setValue(value);
                    updated = true;
                }
                if(! updated) { // 首節點未更新,遍歷之後結點
                    while(p.next != null) {
                        if(key.equals(p.next.getKey())) {
                            oldV = p.next.setValue(value);
                            updated = true;
                            break;
                        }
                        p = p.next;
                    }
                }
                if(! updated) { // 沒有更新過,插入新節點
                    Node<K, V> node = new Node<>(h, key, value);
                    p.next = node;
                }
            }
        }
        // 5、更新modCount
        ++modCount;
        // 6、更新size
        if(! updated) // 沒有更新,說明新增結點
            ++size;
        // 7、檢查擴容
        if(size > threshold)
            resize();
        return oldV;
    }
    public V remove(Object key) {
        Node<K, V>[] tab = table;
        // 1、二次哈希
        int h = hash(key);
        // 2、計算位置
        int n = tab.length;
        int i = getIndex(h, n);
        // 3、遍歷桶中結點
        Node<K, V> p = tab[i];
        if(p == null) // 桶爲空
            return null;
        if(key == null) {
            // 首節點
            if(key == p.getKey()) {
                V value = p.getValue();
                tab[i] = p.next;
                return value;
            }
            // 之後的結點
            while(p.next != null) {
                if(key == p.next.getKey()) {
                    V value = p.next.getValue();
                    p.next = p.next.next;
                    return value;
                }
            }
        }
        else {
            // 首節點
            if(key.equals(p.getKey())) {
                V value = p.getValue();
                tab[i] = p.next;
                return value;
            }
            // 之後的結點
            while(p.next != null) {
                if(key.equals(p.next.getKey())) {
                    V value = p.next.getValue();
                    p.next = p.next.next;
                    return value;
                }
            }
        }
        return null;
    }
    public Set<Entry<K, V>> entrySet() {
        if(entrySet == null) {
            entrySet = new EntrySet();
        }
        return entrySet;
    }
    private Set<Map.Entry<K,V>> entrySet;
    class EntryIterator implements Iterator<Entry<K, V>> {
        Node<K, V> next;        // 下一個結點
        Node<K, V> current;     // 當前結點
        // 快速失敗機制,如果和modCount不一樣,則拋異常
        int expectedModCount;
        int index;              // 迭代到哪一個桶
        EntryIterator() { // 構造方法
            expectedModCount = modCount;
            current = next = null;
            // 找出第一個結點 
            index = 0;
            while((next = table[index]) == null) {
                index++;
            }
        }
        public Entry<K, V> next() {
            // 如果modCount與原先不同,說明已被修改,拋出異常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // 如果next爲null,說明已經迭代到末尾,拋出異常
            Node<K, V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            // 找出下一個結點
            if ((next = (current = e).next) == null)
                while(++index < table.length && (next = table[index]) == null) {}
            return e;
        }
        public final void remove() {
            Node<K,V> p = current;
            // 當前爲null,說明已經remove過,無法再remove
            if (p == null)
                throw new IllegalStateException();
            // 如果modCount與原先不同,說明已被修改,拋出異常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            // 調用MyHashMap的remove方法來移除
            MyHashMap.this.remove(key);
            expectedModCount = modCount;
        }
        public final boolean hasNext() {
            return next != null;
        }
    }
    class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { MyHashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Node<K, V> candidate = getNode(key);
                if(candidate != null && candidate.equals(e)) {
                    MyHashMap.this.remove(key);
                    return true;
                }
                return false;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            // 此方法不做討論,省略
            return null;
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            if (action == null)
                throw new NullPointerException();
            Iterator<Entry<K, V>> it = iterator();
            while(it.hasNext())
                action.accept(it.next());
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章