HashMap是开发中使用非常频繁的键值对形式的工具类,主要是用起来十分方便,但是需要注意的是HashMap他不是线程安全的哦,多线程场景下可以使用ConcurrentHashMap🔍,面试中经常会提到HashTable,但是已经过时了的一个工具,虽说线程安全但是性能低下,HashMap从Java 8开始,源码做了一定的修改,以此来提升其性能;
以上是HashMap的一个数据结构:整体上可以看作是数组+链表的形式。数组是为了快速检索,如果hash函数冲突了的话,就会在同一位置后面进行挂链表的操作。也就是说,同一个链表上的节点,他们的hash值计算出来是一样的。但是如果hash冲突比较多的时候,生成的链表也会拉得比较长,这个时候检索起来就会退化成便利操作,性能就比较低了,在Java 8中为了改善这种情况,引入了红黑树。
红黑树是一种高级的平衡二叉树,其能保证查找、插入、删除的时间复杂度最坏为0(logn)。在大数据量的场景下,相比于AVL树,红黑树的插入删除性能要更高。当链表中的节点数量大于等于8的时候,同时当前数组中的长度大于等于MIN_TREEIFY_CAPACITY时,链表中的所有节点会被转化成红黑树,而如果当前链表节点的数量小于等于6的时候,红黑树又会被退化成链表。
其中MIN_TREEIFY_CAPACITY的值为64,也就是说当前数组中的长度(也就是桶bin的个数)必须大于等于64的时候,同时当前这个链表的长度大于等于8的时候,才能转化。如果当前数组中的长度小于64,即使当前链表的长度已经大于8了,也不会转化
那么为什么不直接用红黑树来代替链表,而是采用链表和红黑树来搭配在一起使用呢?
原因就在于红黑树虽然性能更好,但是这也仅是在大数据量下才能看到差异。如果当前数据量很小,就几个节点的话,那么此时显然用链表的方式会更划算。因为要知道红黑树的插入和删除操作会涉及到大量的自旋,以此来保证树结构的平衡。如果数据量小的话,插入删除的性能高效根本抵消不了自旋操作所带来的成本。
还有一点需要留意的是链表转为红黑树的阈值是8,而红黑树退化成链表的阈值是6。为什么这两个值会不一样呢?
可以试想一下,如果这两个值都为8的话,而当前链表的节点数量为7,此时一个新的节点进来了,计算出hash值和这七个节点的hash值相同,即发生了hash冲突。于是就会把这个节点挂在第七个节点的后面,但是此时已经达到了变成红黑树的阈值了(MIN_TREEIFY_CAPACITY条件假定也满足),于是就转成红黑树