put方法逻辑:
- 如果 HashMap 的数组 table 为 null,则根据当前容量计算数组大小,创建一个新的数组。
- 如果 key 为 null,则新建一个 Entry 对象,并将其插入到 table[0] 中(链表的头部)。
- 如果 key 不为 null,则通过 hash(key.hashCode()) 方法计算出该键的哈希值 h,然后通过 h & (table.length - 1) 计算出该键应该存储在哪个桶中。如果该桶为空,则新建一个 Entry 对象,并将其插入到该桶中;否则,遍历该桶中的链表,查找是否已经存在相同的键。如果存在,则用新的值替换旧的值;否则,新建一个 Entry 对象,并将其插入到链表的末尾。
- 如果当前 HashMap 中的元素数量超过了负载因子(默认为 0.75),则需要进行扩容。扩容时,将数组大小增加一倍,并将原有的所有 Entry 对象重新分配到新的桶中。
扩容逻辑:
- 创建一个新的 Entry 数组 table2,并将其大小设置为原来的两倍。
- 将原来的所有 Entry 对象重新分配到 table2 中。具体来说,遍历原来的数组 table 中的每个非空桶,将其中的每个 Entry 对象重新计算哈希值和目标桶的位置,并插入到 table2 对应的桶中。如果一些 Entry 对象经过重新计算哈希值后仍然位于原有的桶内,则将它们添加到该桶的链表末尾。
- 令 table 引用 table2。这样,HashMap 就开始使用新的哈希表了。
- 如果需要的话,调整阈值 threshold 的大小。
扩容时机:
- 如果新添加的键值对后,元素数量已经超过了阈值0.75,则需要进行乘2扩容。
- 调用 resize() 方法将哈希表大小增加一倍。
链表什么时候转换成红黑树:
- 首先判断长度是否大于64且链表长度是否大于8,如果是则转换,如果小于长度64则进行扩容
- 如果桶中第一个节点就是链表的头结点,遍历整个链表,并将每个节点替换为红黑树上的节点。
- 如果转化后的红黑树仅有一个节点,则将其恢复为链表。
Get方法实现逻辑:
- 如果数组 table 为空,则直接返回 null。
- 如果 key 为 null,则返回 table[0] 中的值(链表的头结点),如果 table[0] 为空,则返回 null。
- 否则,计算键的哈希值和桶的下标,并遍历该桶中的链表。如果找到相同的键,则返回对应的值;否则,返回 null。