1. 导读
这次的分享是关于HashMap::put, 主要是围绕下面几个方面展开:
.1 HashMap::put源码解析;
.2 红黑树插入的处理;
.3 红黑树与链表的互转;
2. HashMap::put源码解析
因为HashMap::put的源码较长, 用下面的流程图来替代:
下面我们一步步来分析:
.1 因为HashMap在初始化的时候, 没有初始化table, 所以在第一次插入时需要初始化table;
.2 判断table[(n - 1) & hash]是否为空, 如果为空则证明是首节点, 直接插入即可;
.3 若不为空, 则需判断挂载的是链表还是红黑树, 若是红黑树, 则走红黑树的插入;
.5 遍历链表, 如果key相同且hash相同, 则直接退出循环;
.6 如果已经到了尾节点, 则直接插入, 再判断链表的长度是否大于8; 若大于8需要转成红黑树;
.7 退出循环后再判断相同key能否覆盖, 能覆盖时直接覆盖, 并返回结果;
.8 插入完成后再判断HashMap.size 是否大于 threshold; 为真时需要扩容;
.9 HashMap::put正常插入的返回结果都为null;
.10 HashMap::put流程中的afterNodeAccess(), afterNodeInsertion()无需关注, 这是LinkedHashMap的处理;
HashMap::put的主流程还是比较清晰的, 但是在JAVA8以后加入了红黑树的处理, 我们需要关注下红黑树的插入与链表转红黑树的操作;
3. 红黑树的插入
HashMap.TreeNode::putTreeVal是红黑树插入的主逻辑:
.1 从根节点遍历槽点的红黑树;
.2 判断待插入节点位于左子树还是右子树, key相等时直接返回, 由主流程判断是否更新节点值;
.3 当遍历到当前节点的左(右)子节点为空时, 插入待插入节点;
.4 再次平衡红黑树;
判断节点位于左右子树的过程在前面的HashMap::get中已经详细的讲解过了, 先比较hash再比较key是否实现了Comparable接口;如果不实现时, 调用HashMap.TreeNode::tieBreakOrder, 我们来看下tieBreakOrder方法做了什么:
tie bread在网球比赛中叫做平局决胜, 如果把判断节点位于那颗子树作为比赛的话:
.1 比较节点的hash值是第一轮;
.2 通过Comparable比较是第二轮;
.3 如果前面两轮没有分出结果, 那么tieBreakOrder就作为决胜轮来比较出一个结果;
.4 当然如果key没有实现Comparable接口, 那么第一轮没结果就会直接进入决胜轮;
我们来看下作为决胜轮的tieBreakOrder方法是如何做到必定出结果的:
static int tieBreakOrder(Object a, Object b) {
int d;
if (