一、概述
- HashMap的底層數據結構是數組,但是數組中存放的并不是一個對象而是鏈表。所以也可以成HashMap的數據結構是哈希桶。在JDK1.8中如果鏈表存放的元素超過8個就將鏈表轉換成紅黑樹。為了保證元素沖突時,查詢和插入效率。Andro中則一直是鏈表。數組每一位存儲的是鏈表的頭部。鏈表的使用時基于哈希表解決沖突(碰撞)使用的方法:鏈地址法
- HashMap默認數組長度(Android:2然后在第一次增加數據后就變成了4,Java:16),默認擴容長度為原來數組長度的一半,數組長度也稱容量。在數組內元素超過閾時,就會進行擴容。閾=負載系數數組長度。負載系數可以自定義也可以使用默認值0.75*;
- 關于hash計算:通過使用key的hashCode(),然后經過哈希函數將hashCode轉換為相應哈希值,通過位運算來計算應該放在表中的哪個位置,位運算效率高于取模運算。哈希函數盡量使得數據的分布均勻。HashMap的Key對象盡量需要復寫父類的hashCode()和equals()方法,這兩個方法主要用于HashMap中存儲。
其中hashCode用于hash值及表中位置的位運算。equals用于判斷key值是否相等。 - 關于擴容以及擴容后的數據位置轉換。首先判斷是否需要擴容。需要擴容,那么容量變為原來的兩倍。擴容后數據需要判斷是否需要向數組的高位移動,判斷方式是元素的hash值與上數組原來的長度(hash&oldCap),如果不為0就需要向高位移動。高位的位置是現在的位置加上oldCap(原來的數組長度)。
- HashMap中重要的兩個參數是容量(Capacity)和負載系數(Load factor)。其中容量是指桶的數目,負載系數是在桶填滿程度的最大比例。這兩個參數關系到了哈希表的性能。Android中負載因子沒有用。。。
二、構造函數及節點信息
2.1 構造函數
關鍵字transient表示實現接口Serializable的對象字段不能被自動序列化
Java
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默認初始化容量,16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量:2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默認負載系數:0.75
transient Node<K,V>[] table;//表,Node數組
transient Set<Map.Entry<K,V>> entrySet;//Map的Entry
transient int size;//大小
transient int modCount;//修改次數
int threshold;//閾,負載系數*容量
final float loadFactor;//負載系數,可自定義
//自定義初始化容量和負載系數的構造函數
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)//如果初始化容量小于0,拋出異常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//如果初始化容量大于最大容量,那么將初始化容量設為最大容量
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//負載系數小于0或者是空
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;//設置負載系數
this.threshold = tableSizeFor(initialCapacity);//設置閾,在第一次put的時候將表的容量設為當前的閾值,并重新修改閾值
}
//只有初始化容量的構造函數
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//默認構造函數,只是將負載因子設為默認參數
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//參數是Map的構造參數
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;//設置負載因子,為默認因子
putMapEntries(m, false);//將map的數據插入進去
}
//將cap的所有除最高位外變為1,然后+1,這樣就變成了大于等于cap的2的n次方
static final int tableSizeFor(int cap) {
int n = cap - 1;//減1,方便處理最高位的值,假定最高位為m,最高位指的是int的二進制表示最高(第一個)1出現的位置,如果這個值的二進制是m位為1,而其他位為0的情況,類似與(10000=16),就是2的n次方。
n |= n >>> 1;//將m-1位置處理為1
n |= n >>> 2;//將m-1到m-4位置處理為1
n |= n >>> 4;//將m-1到m-8位置處理為1
n |= n >>> 8;//將m-1到m-16位置處理為1
n |= n >>> 16;//將m-1到m-32位置處理為1
//其實就是將最高位外全部處理為1,具體參考下圖演示的0,15,16,17
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
Android
private static final int MINIMUM_CAPACITY = 4;//最小的數組長度
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final Entry[] EMPTY_TABLE
= new HashMapEntry[MINIMUM_CAPACITY >>> 1];//空數組,長度為2
static final float DEFAULT_LOAD_FACTOR = .75F;//默認的負載因子
transient HashMapEntry<K, V>[] table;//表,使用HashMapEntry的表
transient HashMapEntry<K, V> entryForNullKey;//專門存儲null鍵的鍵值對對象
transient int size;//size
transient int modCount;//修改次數
private transient int threshold;//閾
private transient Set<K> keySet;//key的集合
private transient Set<Entry<K, V>> entrySet;//entry的集合
private transient Collection<V> values;//值的集合
public HashMap() {//默認構造函數
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;//將table設置空數組
//閾,在第一次增加元素的時候會進行數組擴容,數組長度也就變成了4
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
//設置初始容量的構造函數
public HashMap(int capacity) {
if (capacity < 0) {//如果容量小于0,拋出異常
throw new IllegalArgumentException("Capacity: " + capacity);
}
if (capacity == 0) {//如果容量為0
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
table = tab;//將table設為空數組
threshold = -1; // Forces first put() to replace EMPTY_TABLE,閾值設為-1,為了第一次增加數據時擴容
return;
}
//容量小于最小容量,就將容量設置最小容量
if (capacity < MINIMUM_CAPACITY) {
capacity = MINIMUM_CAPACITY;
} else if (capacity > MAXIMUM_CAPACITY) {//容量大于最大容量,就設為最大容量
capacity = MAXIMUM_CAPACITY;
} else {//變成2的n次方
capacity = Collections.roundUpToPowerOfTwo(capacity);
}
//創建數組,閾值為數組的0.75
makeTable(capacity);
}
//設置初始容量,負載因子沒有用
public HashMap(int capacity, float loadFactor) {
this(capacity);//調用容量函數,負載因子并沒有用,只是為了符合java的構造方法
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor: " + loadFactor);
}
}
//Map的數組
public HashMap(Map<? extends K, ? extends V> map) {
this(capacityForInitSize(map.size()));//調用容量相關的構造函數
constructorPutAll(map);
}
//使用容量,制造數組
private HashMapEntry<K, V>[] makeTable(int newCapacity) {
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] newTable
= (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
table = newTable;//將數組設為新創建的數組
//調整閾值
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
return newTable;
}
static int capacityForInitSize(int size) {
//將size擴容一半
int result = (size >> 1) + size; // Multiply by 3/2 to allow for growth
//~(MAXIMUM_CAPACITY-1)=1<<31 + 1<<30
// (result & ~(MAXIMUM_CAPACITY-1))==0 ,true表示小于1<<30
// boolean expr is equivalent to result >= 0 && result<MAXIMUM_CAPACITY
return (result & ~(MAXIMUM_CAPACITY-1))==0 ? result : MAXIMUM_CAPACITY;
}
Collections.java
//具體的算法,和Java中相同
public static int roundUpToPowerOfTwo(int i) {
i--; // If input is a power of two, shift its high-order bit right.
// "Smear" the high-order bit all the way to the right.
i |= i >>> 1;
i |= i >>> 2;
i |= i >>> 4;
i |= i >>> 8;
i |= i >>> 16;
return i + 1;
}
2.2 節點對象
Java
Java在1.8中有兩種Node節點:普通Node(用于桶的鏈表)和樹的Node(用于紅黑樹節點)
TreeNode的節點繼承自普通Node(在JDK1.8中實際是孫子)。
普通Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//哈希值
final K key;//Key
V value;//值
Node<K,V> next;//下一個節點
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//獲取Key
public final K getKey() { return key; }
public final V getValue() { return value; }//獲取值
public final String toString() { return key + "=" + value; }//轉換成字符串
public final int hashCode() {//哈希值
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//設置值
public final V setValue(V newValue) {
V oldValue = value;//老值
value = newValue;//值設置為新值
return oldValue;//返回老值
}
//判斷是否相等
public final boolean equals(Object o) {
if (o == this)//是否相等
return true;
if (o instanceof Map.Entry) {//判斷對象是否是Map的鍵值對
Map.Entry<?,?> e = (Map.Entry<?,?>)o;//將對象轉換成相應的對象
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;//key和值相等,鍵值對相等。
}
return false;
}
}
紅黑樹的Node
這個紅黑樹使用Hash值進行比較,如果Hash相同,那么再使用key值進行比較。key值需要比較功能。
紅黑樹的增加刪除需要平衡樹,關于紅黑樹,參考鏈接內的內容。
紅黑樹的根節點放入哈希表中。
關于節點為啥不講紅黑樹節點:因為也沒太看懂Java代碼。這里的紅黑樹表現會比較復雜。
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
Android
HashMap條目,鏈表的節點
static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;//哈希值
HashMapEntry<K, V> next;//下一個節點
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
public final K getKey() {//獲取Key
return key;
}
public final V getValue() {//獲取值
return value;
}
public final V setValue(V value) {//設置值
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public final boolean equals(Object o) {//判斷是否相等
if (!(o instanceof Entry)) {
return false;
}
Entry<?, ?> e = (Entry<?, ?>) o;//key和value相等,那么Entry相等
return Objects.equal(e.getKey(), key)
&& Objects.equal(e.getValue(), value);
}
@Override public final int hashCode() {//哈希值
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
@Override public final String toString() {//轉換成字符串
return key + "=" + value;
}
}
三、增加元素
3.1 增一個元素:增加一個鍵值對
將一個元素放入哈希表
Java
public V put(K key, V value) {
//根據key的hashCode(),求出key的哈希值。
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果tab是空,或者tab的長度為0,需要擴容
//默認構造函數創建的table就是空
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//tab重新設置大小,并將長度賦值
if ((p = tab[i = (n - 1) & hash]) == null)//計算key對應hash位置是否為空
tab[i] = newNode(hash, key, value, null);//如果為空表示,這個位置沒有值,直接設置新值即可。
else {
Node<K,V> e; K k;//
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//哈希值、key相等表示放入的鍵值對需要修改
else if (p instanceof TreeNode)//如果是樹節點,就采用紅黑樹的插入方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//鏈表的插入方法
for (int binCount = 0; ; ++binCount) {//循環鏈表
if ((e = p.next) == null) {//將p向后移動,直到p.next空
p.next = newNode(hash, key, value, null);//將p的后節點指向新創建的節點
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//如果count=7,表示插入之后達到了8個,將數據結構換成鏈表
break;
}//如果值相等,打斷循環
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}//如果e不為空,表示找到了e,將e的值修改掉。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//增加修改次數
if (++size > threshold)//如果size大于閾值,需要進行擴容。
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//老table
int oldCap = (oldTab == null) ? 0 : oldTab.length;//老的容量
int oldThr = threshold;//老的閾值
int newCap, newThr = 0;//新的長度和新的閾值
if (oldCap > 0) {//如果老表的長度大于0
if (oldCap >= MAXIMUM_CAPACITY) {//如果老的容量大于設定的最大容量
threshold = Integer.MAX_VALUE;//將閾值設為Integer的最大值,2的31次方-1;
return oldTab;//返回老表
}//新容量=老容量*2,如果新容量小于最大容量值并且老容量大于等于默認容量值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold,將閾值double之后直接設為新閾值
}//如果老閾值大于零,并且老的容量小于0,將初始容量設為閾值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;//構造函數使用設置初始容量相關的函數
else { // zero initial threshold signifies using defaults
//默認構造函數,第一執行put操作的時候。
newCap = DEFAULT_INITIAL_CAPACITY;//將容量設置為初始容量
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的閾值為默認的負載因子*容量
}
if (newThr == 0) {//如果新的閾值為0,重新設定新閾值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//將閾值賦值
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//創建新的數組
table = newTab;//將table指向新創建的數組
if (oldTab != null) {//如果老數組不為空
for (int j = 0; j < oldCap; ++j) {//循環遍歷數組
Node<K,V> e;//臨時記錄節點
if ((e = oldTab[j]) != null) {//記錄數組中相應位置的節點,并判斷是否為空,如果為空,直接跳過。不為空才進行處理
oldTab[j] = null;//將老數組中相應位置元素置空
if (e.next == null)//如果e沒有下一個元素,表示這個數組內放置的只有一個元素
newTab[e.hash & (newCap - 1)] = e;//將元素移動到新數組對應位置即可
else if (e instanceof TreeNode)//如果節點是樹,那么久使用紅黑樹的拆分
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order,鏈表的拆分
Node<K,V> loHead = null, loTail = null;//低位區,頭尾“指針”
Node<K,V> hiHead = null, hiTail = null;//高位區,頭尾“指針”
Node<K,V> next;//下一個
do {
next = e.next;//將next后移,然后處理e
if ((e.hash & oldCap) == 0) {//如果計算結果值為0,表示處于低位
if (loTail == null)//沒有尾指針
loHead = e;//將頭部指針指向e
else//將尾部的下一個指向e
loTail.next = e;
loTail = e;//將尾部指針指向e
}
else {//如果計算結果不為0,表示處于高位
if (hiTail == null)//高位頭指針為空
hiHead = e;//高位頭指針指向e
else//高位頭指針不為空
hiTail.next = e;//高位尾指針下一個指向e
hiTail = e;//高位尾指針移向e
}
} while ((e = next) != null);//將e重新指向空指針
if (loTail != null) {//低位尾指針不為空
loTail.next = null;//將尾指針下一位置空
newTab[j] = loHead;//將對應新數組位置設置為低位頭指針
}
if (hiTail != null) {//如果高位尾指針不為空
hiTail.next = null;//將高位尾指針下一位置空
newTab[j + oldCap] = hiHead;//將新數組高位對應位置指向高位尾指針。
}
}
}
}
}//如果為空,直接返回新創建的數組
return newTab;
}
static final int hash(Object key) {
int h;
//如果key不為null,將key的hashCode亦或上key的HashCode
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
JDK1.8的相關代碼
HashMap<Integer, Integer> hp = new HashMap<>();
hp.put(0, 0);
hp.put(16, 16);
hp.put(32, 32);//0,16,32,在沒有擴容前,都放在0位置上
hp.put(1, 1);
hp.put(31, 16);
hp.put(2, 2);
hp.put(3, 3);
hp.put(4, 4);
hp.put(5, 5);
hp.put(6, 6);
hp.put(7, 7);
hp.put(8, 8);//此時size是12 ,容量是16,在插入9之后進行擴容
hp.put(9, 9);//在擴容后,16因為需要變化需要重新換位置,換到了高位上。
關于容量從16變化到32
判斷是否需要重新換位置的標準是(hash&新容量-1)是否可以在原來的位置上。
但是上面的判斷變化非常復雜,從而采用了一種簡單的變化標準,就是下圖中的變化。
其實判斷的核心標準在于新增的高位變化了1,就是從原來的判斷4位到判斷5位。就是最高位的判斷。其中判斷新變化的第5位是否是1即可,因為是1表示需要移動位置。因為容量變為32之后,位置考慮的也只是hash值后5位。所以hash&oldCap!=0就是表示需要換位的相關元素,將它們拼成新的鏈表
關于新位置為什么使用[j + oldCap],是因為只變化了最高位,這個最高位為1低位全為0在二進制上表示就oldCap
Android
使用了一個固定的節點對象,來存儲null鍵的情況。
沒有特別復雜的算法,一切都是看起來那么簡單。。。
@Override public V put(K key, V value) {
if (key == null) {//插入空鍵
return putValueForNullKey(value);
}
//計算Hash值,這個hash值的計算需要
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);//計算表中的位置
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {//判斷這位置是否有數據,如果沒有向后移動
if (e.hash == hash && key.equals(e.key)) {//找到hash值相等和key相等的數據
preModify(e);//預修改數據
V oldValue = e.value;//找到了,修改數據
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {//如果size增加后,大于了閾值
tab = doubleCapacity();//容量擴容
index = hash & (tab.length - 1);//修改位置
}
addNewEntry(key, value, hash, index);//新增加鍵值對
return null;
}
private V putValueForNullKey(V value) {
HashMapEntry<K, V> entry = entryForNullKey;//空鍵節點
if (entry == null) {//如果不存在空鍵節點,創建一個
addNewEntryForNullKey(value);
size++;
modCount++;
return null;
} else {//如果存在空鍵節點,修改空鍵節點
preModify(entry);//預修改,主要針對LinkeHash修改的方法,在HashMap中是空方法。
V oldValue = entry.value;//修改值
entry.value = value;
return oldValue;
}
}
void addNewEntryForNullKey(V value) {
//創建一個空鍵的HashMapEntry
entryForNullKey = new HashMapEntry<K, V>(null, value, 0, null);
}
void preModify(HashMapEntry<K, V> e) { }
//在相應的位置創建一個新的條目即可
void addNewEntry(K key, V value, int hash, int index) {
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}
//擴容,雙倍擴容
private HashMapEntry<K, V>[] doubleCapacity() {
HashMapEntry<K, V>[] oldTable = table;//老表
int oldCapacity = oldTable.length;//老容量
if (oldCapacity == MAXIMUM_CAPACITY) {//如果老的容量已經等于最大值就返回老表
return oldTable;
}
int newCapacity = oldCapacity * 2;//新容量=老容量擴大2倍,因為初始容量都是2的倍數,最大容量也是2的倍數,所以擴容后不會超過
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);//確保新表容量是2的倍數
if (size == 0) {//如果size=0,不需要處理表中的數據
return newTable;//直接將新表返回
}
for (int j = 0; j < oldCapacity; j++) {//遍歷老表
/*
* Rehash the bucket using the minimum number of field writes.
* This is the most subtle and delicate code in the class.
*/
HashMapEntry<K, V> e = oldTable[j];//記錄位置的數據
if (e == null) {//如果數據為空,繼續
continue;
}
int highBit = e.hash & oldCapacity;//鏈表首位數據高位的bit,其他位全是0
HashMapEntry<K, V> broken = null;//相等于尾節點
newTable[j | highBit] = e;// j | highBit等于j+highBit,因為j和highBit的位值有差異
//循環鏈表
for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
int nextHighBit = n.hash & oldCapacity;//計算鏈表其他數據的bit
if (nextHighBit != highBit) {//如果不相等,表示需要移動位置
if (broken == null)//如果broken為空,表示沒有移動過
newTable[j | nextHighBit] = n;//直接移動數據即可
else//移動過數據,就將broken的后位置為n即可
broken.next = n;
broken = e;//將broken指向e,具體參考下圖的演示
highBit = nextHighBit;//將高位置移動
}
}
if (broken != null)//如果broken不為空,將broken的后續節點指向空
broken.next = null;
}
return newTable;
}
Collections.java
public static int secondaryHash(Object key) {
return secondaryHash(key.hashCode());
}
private static int secondaryHash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
//-12931
//1111 1111 1111 1111 1100 1101 0111 1101
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
3.2 增加Map中的元素
Java
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {//map中的數據大于0個
if (table == null) { // pre-size,主要針對空數組預加載閾值
float ft = ((float)s / loadFactor) + 1.0F;//這個值是map種的容量
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);//設定容量
if (t > threshold)//如果map中容量超過閾值,修改閾值
threshold = tableSizeFor(t);
}
else if (s > threshold)//如果容量查過閾值,那么修改擴容
resize();
//遍歷,并將map中數據插入到HashMap中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
Android
循環一個個增加元素
@Override public void putAll(Map<? extends K, ? extends V> map) {
ensureCapacity(map.size());//確保容量足夠
super.putAll(map);//調用父類的putAll方法
}
private void ensureCapacity(int numMappings) {
int newCapacity = Collections.roundUpToPowerOfTwo(capacityForInitSize(numMappings));//將容量擴容
HashMapEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (newCapacity <= oldCapacity) {//確保需要的容量小于已有容量,直接返回
return;
}
if (newCapacity == oldCapacity * 2) {//如果新容量是老容量的2倍
doubleCapacity();//直接擴容,然后返回
return;
}
// We're growing by at least 4x, rehash in the obvious way
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
//新創建數組
if (size != 0) {
int newMask = newCapacity - 1;//新容量-1
for (int i = 0; i < oldCapacity; i++) {
for (HashMapEntry<K, V> e = oldTable[i]; e != null;) {
HashMapEntry<K, V> oldNext = e.next;//記錄e的后節點
int newIndex = e.hash & newMask;//計算新位置
HashMapEntry<K, V> newNext = newTable[newIndex];//記錄新位置的數據
newTable[newIndex] = e;//將新位置的數據設為e
e.next = newNext;//將e后繼指向原來新位置的數據
e = oldNext;//將e指向原來e的后繼,循環鏈表
}
}
}
}
AbstractMap.java
public void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
四、刪除元素
4.1刪除Key相等的元素
在key相等的情況下就刪除元素,默認的刪除元素的方法
還有一個刪除key相等和Value相等的元素方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
//哈希值、鍵、值、是否需要值相等、移動的(用于紅黑樹)?
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//表,相應位置p節點,表長度,位置
Node<K,V>[] tab; Node<K,V> p; int n, index;
//表不為空,表長度大于0,表相應位置元素不為空
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
//需要刪除的節點
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;//hash值相等和key相等,表示找到了元素
else if ((e = p.next) != null) {//沒有直接找到元素,處理鏈表或紅黑樹
if (p instanceof TreeNode)//處理紅黑樹,找到節點
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {//處理鏈表
do {//找到hash和key相等的記錄為node
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;//刪除位置前的元素
} while ((e = e.next) != null);
}
}
//判斷是否可以刪除
// 節點不為空,是否需要值相等
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)//刪除樹的節點
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)//如果刪除的是鏈表的頭節點
tab[index] = node.next;//將數組指向鏈表的下一位置即可
else//將前節點的后繼指向刪除節點的后繼
p.next = node.next;
++modCount;//修改次數增加
--size;//size減小
afterNodeRemoval(node);//LinkeHashMap使用
return node;
}
}
return null;
}
Android
@Override public V remove(Object key) {
if (key == null) {//如果key等于null,調用null的刪除方法
return removeNullKey();//移除空元素
}
int hash = Collections.secondaryHash(key);//計算Hash值
HashMapEntry<K, V>[] tab = table;//表
int index = hash & (tab.length - 1);//計算位置
//循環鏈表,記錄節點的前節點
for (HashMapEntry<K, V> e = tab[index], prev = null;
e != null; prev = e, e = e.next) {
//hash值相等和key相等,表示找到了元素,
if (e.hash == hash && key.equals(e.key)) {
if (prev == null) {//如果沒有前節點,表示需要刪除的鏈表的尾節點
tab[index] = e.next;
} else {//如果有將前節點的后節點指向新的后節點
prev.next = e.next;
}
modCount++;//修改次數增加
size--;//size-1
postRemove(e);//預刪除
return e.value;
}
}
return null;
}
private V removeNullKey() {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null) {//如果e為null,表示沒有存儲空鍵值的元素
return null;
}
entryForNullKey = null;//將空元素置為空
modCount++;//修改次數
size--;//size減小
postRemove(e);//預刪除,子類可以能有相應取消連接的實現
return e.value;
}
void postRemove(HashMapEntry<K, V> e) { }
五、修改元素
Java
除put外還有replace相關方法,
@Override
public V replace(K key, V value) {
Node<K,V> e;
//找到節點病替換節點的值
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
//找到key和value都相等的節點,替換這個節點的值
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Node<K,V>[] tab;
if (function == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
//循環遍歷數組,處理數組中的數據
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
e.value = function.apply(e.key, e.value);
}
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
Android
沒有replace相關的API
六、查詢元素
Java
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
@Override
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;//如果查找的結果為空,就返回給定的默認值
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//找到hash對相應位置的元素
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//檢查第一個節點key是否相等
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {//是否有后繼節點
if (first instanceof TreeNode)//如果是樹形結構,調用樹形結構的獲取方法
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {//循環遍歷鏈表,直到找到相應的節點
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
Android
除了紅黑樹外與Java沒有特殊差異
public V get(Object key) {
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
七、其他
7.1 包含元素
Java
//包含Key
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
//是否包含值
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
//循環遍歷數組、鏈表以及紅黑樹
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
Android
@Override
public boolean containsKey(Object key) {
if (key == null) {
return entryForNullKey != null;
}
//計算hash值
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
//遍歷tab對應的鏈表
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return true;
}
}
return false;
}
@Override
public boolean containsValue(Object value) {
HashMapEntry[] tab = table;
int len = tab.length;
if (value == null) {//如果值為空,先遍歷數組中是否有null值,如果有返回true,如果沒有查詢空鍵值對中是否包含值
for (int i = 0; i < len; i++) {
for (HashMapEntry e = tab[i]; e != null; e = e.next) {
if (e.value == null) {
return true;
}
}
}
return entryForNullKey != null && entryForNullKey.value == null;
}
// value is non-null,循環遍歷判斷是否包含相應的元素
for (int i = 0; i < len; i++) {
for (HashMapEntry e = tab[i]; e != null; e = e.next) {
if (value.equals(e.value)) {
return true;
}
}
}
//最后判斷空鍵值對的值是否是需要查找的值
return entryForNullKey != null && value.equals(entryForNullKey.value);
}
7.2 集合的Size
Java
public int size() {
return size;
}
Android
@Override public int size() {
return size;
}
7.3 集合是否為空
Java
public boolean isEmpty() {
return size == 0;
}
Android
@Override public boolean isEmpty() {
return size == 0;
}
7.4 清空
Java
循環變量數組將數組元素置為空,紅黑樹和鏈表等待JVM自動回收
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
Android
使用了工具類,將數組及相應鏈表內的數據全部填充為空
@Override
public void clear() {
if (size != 0) {
Arrays.fill(table, null);
entryForNullKey = null;
modCount++;
size = 0;
}
}
八、遍歷
8.1 鍵值對迭代器
Java
對應EntrySet的迭代器
//鍵值對Set
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
//鍵值對Set繼承了抽象的Set
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }//返回HashMap的大小
public final void clear() { HashMap.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();//獲取Key
Node<K,V> candidate = getNode(hash(key), key);//獲取節點的鍵值對
return candidate != null && candidate.equals(e);//判斷節點是否相等
}
public final boolean remove(Object o) {//移除鍵值對
if (o instanceof Map.Entry) {//如果是Map的鍵值對
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
//找到鍵和值相等的鍵值對,并移除它
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
//拆分哈希表
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
//Entry的鍵值對,繼承了默認鍵值對
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class HashIterator {
Node<K,V> next; // next entry to return,下一個節點
Node<K,V> current; // current entry,當前節點,返回的節點
int expectedModCount; // for fast-fail,期望修改次數
int index; // current slot,當前的槽
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {//是否有下一個
return next != null;//下一個不為空
}
final Node<K,V> nextNode() {//下一個節點
Node<K,V>[] t;//
Node<K,V> e = next;//下一個節點
if (modCount != expectedModCount)//判斷修改次數是否變化過
throw new ConcurrentModificationException();
if (e == null)//節點不為空,表示還有后續節點
throw new NoSuchElementException();
//如果next是null,就將next賦值為表中下一個不為null的值
//如果next不是null,那么next就是鏈表或者紅黑樹中的下一個值
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
//移除元素
public final void remove() {
Node<K,V> p = current;//當前節點
if (p == null)//如果當前節點為空,拋出異常
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;//將當前節點置為null,避免連續兩次調用移除,并且不移動
K key = p.key;//key值相等
//移除node節點
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
Android
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
private final class EntrySet extends AbstractSet<Entry<K, V>> {
public Iterator<Entry<K, V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>) o;
return containsMapping(e.getKey(), e.getValue());
}
public boolean remove(Object o) {
if (!(o instanceof Entry))
return false;
Entry<?, ?> e = (Entry<?, ?>)o;
return removeMapping(e.getKey(), e.getValue());
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
HashMap.this.clear();
}
}
Iterator<Entry<K, V>> newEntryIterator() { return new EntryIterator(); }
private final class EntryIterator extends HashIterator
implements Iterator<Entry<K, V>> {
public Entry<K, V> next() { return nextEntry(); }
}
private abstract class HashIterator {
int nextIndex;
HashMapEntry<K, V> nextEntry = entryForNullKey;
HashMapEntry<K, V> lastEntryReturned;
int expectedModCount = modCount;
HashIterator() {
if (nextEntry == null) {
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = null;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
}
}
public boolean hasNext() {
return nextEntry != null;
}
HashMapEntry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == null)
throw new NoSuchElementException();
HashMapEntry<K, V> entryToReturn = nextEntry;
HashMapEntry<K, V>[] tab = table;
HashMapEntry<K, V> next = entryToReturn.next;
while (next == null && nextIndex < tab.length) {
next = tab[nextIndex++];
}
nextEntry = next;
return lastEntryReturned = entryToReturn;
}
public void remove() {
if (lastEntryReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
HashMap.this.remove(lastEntryReturned.key);
lastEntryReturned = null;
expectedModCount = modCount;
}
}
8.2 Key迭代器
Java
public Set<K> keySet() {
Set<K> ks;
return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}
//與EntrySet相同,只不過處理的Entry
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
Android
@Override
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 isEmpty() {
return size == 0;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
int oldSize = size;
HashMap.this.remove(o);
return size != oldSize;
}
public void clear() {
HashMap.this.clear();
}
}
Iterator<K> newKeyIterator() { return new KeyIterator(); }
private final class KeyIterator extends HashIterator
implements Iterator<K> {
public K next() { return nextEntry().key; }
}
8.3 值迭代器
Java
public Collection<V> values() {
Collection<V> vs;
return (vs = values) == null ? (values = new Values()) : vs;
}
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
Android
@Override 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 isEmpty() {
return size == 0;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
Iterator<V> newValueIterator() { return new ValueIterator(); }
private final class ValueIterator extends HashIterator
implements Iterator<V> {
public V next() { return nextEntry().value; }
}
8.4 小結
- 三個迭代器都是一個迭代器的子類
- 如果遍歷Map的話,建議使用EntrySet,這個效率稍高,不用再次調用get方法獲取Value值
- Java和Android大同小異,除了Java多了一種數據結構紅黑樹
九、總結
- 關于底層數據結構:哈希桶(每個桶里放置鏈表或紅黑樹)
- 關于容量和負載因子:容量是哈希表的最大容量,負載因子是哈希Map的存儲比例
閾值是最大容量*負載因子。而判斷是否超過閾是采用size進行判斷,并不是hashTable中數組的占有比例 - 影響哈希Map的效率最主要的方法是key對應類實現的hashCode。
- 可以設置key值為null和value值為null
- 與HashTable的區別
4.1. HashMap是線程非安全的,HashTable是線程安全的,Hashtable不允許key和value值為null,
4.2. 關于Hash值,HashMap中使用了一個函數來處理key.hashCode()方法,Hashtable直接使用了key.hashCode()方法作為hash值,這樣也是為什么key值不能為null,表的位置信息是采用模運算,因為表的長度不是2的n次方。
4.3. Hashtable擴容是2倍+1,表的長度最小可以設為1。如果使用默認構造函數,那么長度是11,可以理解為默認長度是11.
參考
其他文章
容器解析
ArrayList解析
LinkedList解析
TreeMap解析(上)
TreeMap解析(下)
HashMap解析
LinkedHasMap解析(下)
Set解析