如果你還不瞭解 HashMap,建議你先看我上一篇博客:
分享 HashMap 的精髓,它永遠比你自己的寫 map 效率高
這篇博客更多的闡述瞭如何去寫,更多的是對代碼的分析,需要有閱讀代碼的能力。
如果你不太擅長閱讀代碼,可以去看我的上一篇博客,裏面更多的是對知識點語言描述,相對會更容易理解。
- 寫HashMap的流程:首先寫Map接口表明要實現哪些方法,實現AbstractMap抽象類實現一些基本方法(如 toString(),isEmpty() 等等),然後寫HashMap類,從內部需要哪些成員開始,定義內部類Node,然後完成構造方法,之後寫入最重要的 put(),get(),remove()三個最重要的方法,最後在實現一些獲取集合等迭代方法。
- 筆者寫的 MyHashMap 與 jdk 源碼並非完全相同,但思路與實現方式相同,在自己寫代碼時,儘量不要去複製粘貼代碼,可以按照方法的思路,儘量做到全部手寫,等你能夠將所有方法無誤實現後,你對 HashMap 的理解一定會很深刻,你的代碼能力也不會僅僅只是停留在重複的業務代碼上。
- 在 jdk 1.8 後,HashMap 裏對數組中每個桶中結點到 8 之後增添了轉化爲紅黑樹的操作,由於紅黑樹的知識過於複雜,比 HashMap 本身都要複雜許多,所以不在這裏介紹,也不在代碼中寫出,實際上,光采用鏈表形式對於默認負載因子,和 2 的次方數數組容量,已經足夠高效了。
- 其中的 Iterator接口,Collection接口,Set接口,這些集合接口在這裏與本文相關程度不高,直接 import jdk 中原有的接口即可。
鍵值對的存儲方式: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 時調用
- 計算新的數組容量和可容納鍵值對數目 newCap newThr
- 根據新的容量創建新的數組
- 將舊數組中的內容全部添加入新數組
該方法比源碼簡略,且不涉及紅黑樹操作,更便於理解
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()方法,三大主方法之一
- 如果存在 key,則更新它對應的 value
- 如果不存在,則插入新的鍵值對
- 如果數組未初始化,先初始化
- 二次哈希值
- 計算數組中桶的位置
- 遍歷桶中結點,如果存在,則更新;否則在尾部插入新節點。
- 更新 modCount
- 更新 size
- 如果 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。
- 二次哈希
- 計算出在數組中對應的桶的位置
- 遍歷桶中結點
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。
- 首先需要有一個 EntrySet 類
- 既然是作爲迭代,那必然先有 EntryIterator 類
- 主要完成 EntryIterator 中的關鍵方法
- EntrySet 中的其他方法大家按需寫即可
- 調用方法時,如果 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());
}
}
}