Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值相同的 Entry。
2. 拉链法的工作原理
HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
(解释:下图代码中,首先把第0个桶的地址赋给e,然后判断这个桶的第一个Entry的key是否等于null,如果等于null,那么就用新值来覆盖旧值,然后把旧值作为函数的返回结果,然后依次遍历第0个桶的各个Entry,如果遍历完了都没有一个key等于null,说明我们要在第0个桶上新添一个key==null的元素,然后把函数参数value作为key对应的值)
小疑问:二进制数之间取模是怎么运算来着?
我们知道,位运算的代价比求模运算小的多,因此在进行这种计算时用位运算的话能带来更高的性能。
确定桶下标的最后一步是将 key 的 hash 值对桶个数取模:hash%capacity,如果能保证 capacity 为 2 的 n 次方,那么就可以将这个操作转换为位运算。
从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。(解释:不要和上面的threshold,loadFactor这两个参数搞绕了,这两个参数只是决定了当存储元素达到多少的时候扩容,而不是决定要扩容的倍数)
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
解释: 因为y%x的结果和y&&(x-1)的结果相等,所以上述图中capacity-1=00001111,new capacity-1=00011111,所以如果一个key的hash值在第5位上为0,那么取模得到的结果和之前是一样的,如果为1,那么得到的结果为原来的结果+16