put方法调用过程详解
流程图
put的包装方法
put的主方法
从中可以看出:
- 是拿size的大小和threshold进行比较来决定是否需要扩容
- loadFactor负载因子是可以大于1的,因为构造函数没有限制,而且从结构上看size的大小是可以大于数组长度的(由于树节点和链表节点的存在)。
扩容方法详解
1.新容量和阀值的计算
2.扩容
注意:由于扩容是以2的倍数进行扩容的,而hash值对(数组长度-1)进行取模计算索引位置是通过掩码的形式。这是因为2倍扩容后在减1与hash进行与,仅仅是前面多了一位,这位可能为0也可能为1,所以在扩容后重新hash时新索引要么是原来的索引要么就是原索引+oldCap,仅仅通过与数组长度进行按位与就能判断。这是Java8不同于Java7的性能优化的地方,仅仅通过查看某一位是1还是0就能得出新索引的位置,很大程度上调高了rehash的效率,而且高位是0还是1又有一定的随机性,能更好的在扩容时尽量散开冲突的键值对。这又是一个数组必须以2的倍数进行扩容的原因啦。
3.对红黑树节点进行rehash
对树节点rehash和对链表rehash过程基本相似,但是对于树节点多了一个判断是否需要将树的桶节点缩减为链表的桶节点的过程,因为扩容时rehash将会减少链表桶节点和树桶节点的元素数量,至少不会增加,所以对于链表节点不用判断扩张为树,而树节点需要判断是否需要缩减为链表节点。
remove方法调用过程详解
如果存在key则删除并返回value,如果不存在则返回null
删除指定key并返回node
注意:remove方法删除键值对并不会将数组的大小缩小,按照不同类型的待删除的节点有不同的处理方式
- 单元素桶节点:则将当前桶节点设置为null。
- 链表的桶节点:则断开被删除的键值对节点,然后重新连接
- 红黑树桶节点:删除键值对节点并调整树结构保持平衡,如果有必要则将红黑树缩减为链表。
迭代器
对于普通桶节点和单向链表桶节点的迭代都比较好理解,但是对于红黑树桶节点的迭代是如何做到的呢?
看一下树节点的定义就明白了
HashMap.TreeNode
LinkedHashMap.Entry
TreeNode节点不仅维护了树节点的链接,也维护了链表节点的链接(通过next向后遍历),也就是说红黑树桶节点,也是一种链表,这样不仅迭代更方便,而且由红黑树转换为单向链表也更为方便了。
还有HashMap中不像TreeMap那样需要构造一个按照key排序的平衡红黑树,** HashMap中的红黑树是按照hash值或者传入的比较器来排序的**
总结
put方法和remove方法时间复杂度在理想情况下(即没有发生冲突)都是常量时间O(1),但是当发生冲突时,时间复杂度为为O(logn) n>= 8,表示的是同一桶中元素的数量(红黑树)