前言
一定要理解是有顺序的很多桶,桶中装的可不是一个元素。桶的数量就是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 twoint 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进制数111...(都是1)。K 1101010101101010010010010100101... (红色部分是2的正整数倍)&2的n次方 - 1 111... (都是1)计算结果: 101... (取出K的红色以外的部分也就是余数,就是我们的目的)这有啥优点呢?其一运算快,其二呢,好处是,如果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;
}