前言:一定要理解是有順序的很多桶,桶中裝的可不是一個元素。桶的數量就是hashmap通常所說的容量(單位是桶)。桶的數量不一定等於數量size(),so很明顯容量不是存放的元素個數。
源碼中顯示的hashmap的容量就是底層table數組的長度
1、初始桶數量:
int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
2、最大的桶數量:
MUST be a power of two <= 1<<30
int MAXIMUM_CAPACITY = 1 << 30; // 最大的power of two
3、load factor:負載因子
float DEFAULT_LOAD_FACTOR = 0.75f;
4、臨界值
threshold // 第一次進來的時候存的就是 初始化桶數量(然後在第一次put的時候,table是空的,然後才進擴容的方法
inflateTable(threshold)進行擴容table同時,修改這個臨界值=capacity * loadFactor
)
5、HashMap的put方法(體會hash取模後,生成鏈表的過程)
public V put(K key, V value) {
if (table == EMPTY_TABLE) { // 映射數組是空的
inflateTable(threshold); // 初始化table數組(底層的table其實初始化的時候還是0,第一次put時候纔在此方法中擴容table。也有道理確實沒必要上來就初始化個table[innitCapital],在你要用的時候再擴大)
// 擴容的方法中 Find a power of 2 >= toSize // 如果傳入的桶數量不是2的倍數,那麼算出離它最近的且比它大的power of two
int capacity = roundUpToPowerOf2(toSize);
}
if (key == null)
return putForNullKey(value); // put到key爲null的v中,且null映射在在table[0]中
int hash = hash(key); // 哈希值
int i = indexFor(hash, table.length); // (hash & table.length-1) 與運算的取模(對length取模),得到table映射數的下標
K % 2的n次方 = K & (2的n次方 - 1) :此算法只適合 冪次方運算,所以hashmap的容量是2的倍數
解釋:2的n次方減一含義就是將這個2進制數000011111111111111111這種類型的數,這樣的話1的個數正好是要取模的位數(比如取後三位),然後與運算就拿到了模值。其二呢,好處是,如果hash值是負數,取模不存在的,還是正數
注:int最大值(2^31-1)所以,所以hashmap最大容量就是(2^30)。
for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 拿到下標是 i 的映射鏈表(桶)對象遍歷
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // key相同,那麼替換返回舊值
此處如果僅僅是hash相同,其他不同,那麼就是hash衝突了。
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; //
}
}
// 如果不存在key或者是hash衝突了,那麼久添加到table[i]下的鏈表中
modCount++;
HashMap結構修改的次數,結構性的修改是指,改變Entry的數量
addEntry(hash, key, value, i); // hash和key相同是不會走到這的,所以hash就算衝突了,他也是在同一個鏈表中,他們的key'是不同的
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) { // 超過桶臨界值且table當前處 不是null,擴容且對key再次hash(key)
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex); // 沒有超過臨界值,就在table當前處創建entry
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex]; // 此處拿到當前處的映射
table[bucketIndex] = new Entry<>(hash, key, value, e); // 同時在創建一個新映射
size++;
}
static class Entry<K,V> implements Map.Entry<K,V> {
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n; // 新的entry就next指向了老的(擠到下面),最下面的entry指向null
key = k;
hash = h;
}
return null;
}