前言:一定要理解是有顺序的很多桶,桶中装的可不是一个元素。桶的数量就是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;
}