public V put(K key, V value) {
//如果table数组为空,进行数组填充(为table分配实际内存空间),入参为threshold
//此时threshold为initCapacity,默认是1<<4(=16)
if (table == EMPTY_TABLE) {
inflateTable(threshold);//分配数组空间
}
//如果存入的键值对键为0,存储位置为table[0]或table[0]的冲突键上
if (key == null)
return putForNullKey(value);
//重新计算key的hashcode,确保散列均匀
int hash = hash(key);
//获取在table中的实际位置
int i = indexFor(hash, table.length);
//遍历table[i] 上的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key重复,执行覆盖操作,用新value代替旧value并返回旧的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);//调用value的回调函数,其实这个函数也为空实现
return oldValue;
}
}
modCount++;//保证并发访问时,若hashmap内部结构发生变化,快速响应失败 fail-fast
//添加新节点
addEntry(hash, key, value, i);
return null;
}
private void inflateTable(int toSize) {
//capacity一定是2的n次幂
int capacity = roundUpToPowerOf2(toSize);
//为threshold赋值,取capacity*loadFacor和MAXIMUM_CAPACITY的最小值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {
//
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
//这个方法计算出小于等于i的最大的2的次幂
比如传入30,二进制表达为 0001 1110
一系列的位或运算后得出: 0001 1111 :31
返回 31-(31 >> 1) = 31- 15 =16;
这就保证了哈希表的数组长度一定是2的n次幂
}
static int indexFor(int h, int length) {
//计算键值对在数组上的位置
//采用hashcode和数组长度减一的位与运算
return h & (length-1);
}
由于hashmap的容量都为2的n次幂
2的n次幂以二进制表示为高位为1其后都为0,例如
16:0001 0000 32: 0010 0000
所以length-1的二进制表现形式为高位0,其后都为1,例如
15:0000 1111 32: 0001 1111
这样在参与位与&运算的时候就保证了得出的结果在 0~length-1之间
因为位与运算的规则是 同时为1则为1 ,否则为0
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果hash表中目前存在的元素个数大于等于阈值并且新插入的键值对所在的数组上
//不为空,就进行数组的扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//新数组的长度为原数组的2倍
resize(2 * table.length);
//计算key的hash值
hash = (null != key) ? hash(key) : 0;
//重新计算元素所应该在的数组下标,结果只可能是两个:原位置或原位置加元素组长度
bucketIndex = indexFor(hash, table.length);
}
//创建一个Entry节点,
createEntry(hash, key, value, bucketIndex);
}
//数组的扩容,传入一个新容量
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果老数组长度已经达到最大长度限制直接结束
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//创建一个长度为newCapacity的新数组
Entry[] newTable = new Entry[newCapacity];
//转移哈希表中的元素到新哈希表
transfer(newTable, initHashSeedAsNeeded(newCapacity));
//将新表赋值给table属性
table = newTable;
//计算新的阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转移原哈希表中的元素到新的哈希表中
void transfer(Entry[] newTable, boolean rehash) {
//获取新数组容量
int newCapacity = newTable.length;
//遍历老哈希表Entry[]数组
for (Entry<K,V> e : table) {
//当这个Entry不为空
while(null != e) {
//将e的下一个节点赋值给next
Entry<K,V> next = e.next;
//一般不需要重新计算哈希值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//重新计算需要在新数组插入的数组下标
int i = indexFor(e.hash, newCapacity);
//将e元素插入到newTable[i]的头上,即将e的next属性指向newTable[i]
e.next = newTable[i];
//将e元素插入到newTable[i]上(头插法)
newTable[i] = e;
//开始转移下一个元素
e = next;
}
}
}
如果length为2的次幂,其二进制表示就是100….0000;则length-1 转化为二进制必定是0111….11的形式,在于h的二进制与操作效率会非常的快,而且空间不浪费;如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,再于h与操作, 最后一位都为0,所以0001,0011,0101,1001,1011,0111,1101这几个位置永远都不会存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。