一、Java基础

(一)HashMap一次put操作过程

1、put(key, value)中直接调用了内部的putVal方法,并且先对key进行了hash操作;

2、putVal方法中,先检查HashMap数据结构中的索引数组表是否位空,如果是的话则进行一次resize操作;

3、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个k-v的新节点;

4、如果不满足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现

5、在上面判断流程走完之后,计算HashMap全局的modCount值,以便对外部并发的迭代操作提供修改的Fail-fast判断提供依据,于此同时增加map中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮resize操作;

6、在步骤4中出现碰撞情况时,从步骤7开始展开新一轮逻辑判断和处理;

7、判断key索引到的节点(暂且称作被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;如果新旧节点的内容不一致时,则再看被碰撞节点是否是树(TreeNode)类型,如果是树类型的话,则按照树的操作去追加新节点内容;如果被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表尚未转为红黑树),此时进入一轮循环处理逻辑中;

8、循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点,作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话,需要进行一轮是否转树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都满足则继续循环,直至进入某一个条件判断然后跳出循环;

9、步骤8中转树的操作treeifyBin,如果map的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行resize操作就行了;否则,如果被碰撞节点不为空,那么就顺着被碰撞节点这条树往后新增该新节点;

10、最后,回到那个被记住的被碰撞节点,如果它不为空,默认情况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)。

1.2 put<k, v>方法流程图

根据上面分析出的流程步骤,我大致画了一个put方法的流程图,以方便理解。

18871813-be251e9d34de0d19.png

1.3 思考与优化

1、resize操作在当前索引表容量不足时发生,这个操作对put性能有一定的冲击(据说还会引起死循环),但是能够自行避免,如果在我们使用map的时候能够知道需要存入的记录数,则可以通过**【 (记录数 / threshold) + 1 】的方式计算出一个map的初始容量,并在声明HashMap时将初始容量指定为这个计算值。多提一句,尽管我们按照这种方式计算出了一个能够最大包容我们预期k-v键值对的容量值,但是HashMap为了性能考虑,在我们初始化容量之后,其内部又使用了一个tableSizeFor的方法将这个值转换成了一个大于等于该值的最近的一个2次幂的数值**,以方便后续其他的位操作,这个方法很巧妙,可以自行研究一下。但这个内部方法我们是不需要考虑和深究的,按照上面这个方法计算并初始化使用就行了。

(二)HashMap扩容

https://zhuanlan.zhihu.com/p/114363420