HashMap

  • 1.HashMap的数据结构,在jdk8之前使用数组+链表,叫做链地址法,jdk8之后使用的是数组+链表+红黑树,数组容易查询难修改,链表慢查询容易修改,所以两者相结合
  • 2.计算完key哈希值之后,要根据key计算出应该对应的数组下标,这个时候就需要对数组的长度进行取模运算,这里采用的是位运算按位与,因为位运算是直接操作内存数据,不需要从十进制转换成二进制,速度更快一些x%2^n==x&(2 ^n -1),所以HashMap的长度都是2的幂
  • **3.HashMap中几个关键的参数:size()表示插入键值对的个数,capacity表示table(也就是存储链表和红黑树节点的数组)的长度初始默认为16,负载因子loadFactor默认0.75(经过数学推算这个负载因子在0.7左右能在时间和空间上做很好的权衡,因为长度是2的幂,所以乘以0.75正好也是一个整数,并且不会有过多的空余位置浪费),threshold记录了扩容的阈值==capacity*loadfactor **
  • 4.HashMap是如何保证capacity是2的幂呢,第一在初始化的时候如果有设置的大小,会通过位运算转换成一个指定值大的第一个2的幂,第二个就是扩容机制,当size数量超过threshold阈值的时候就会进行扩容
  • 5.在阿里巴巴开发手册上强调过集合类初始化必须指定集合大小,HashMap指定大小最好是 期待空间大小值/负载因子+1这么大,能够有效减少扩容的机制
  • 6.扩容容量是原来的2倍,如果桶节点没有形成链表就直接rehash,如果有链表,就要对节点进行重新链接,如果链表已经形成了树,就取消树化。扩容之后的节点位置要不然就是原位置,要不然就是原索引+原容量,无需重新计算hash值
  • 7.计算hash值的时候采用hash扰动,如果两个key二进制高位不同但是低位相同就很容易产生hash冲突,所以hash扰动就是拔高位置信息混入到低位值,目前jdk8是把计算的hashCode右移16位
  • 8.引入红黑树原因就是如果hash冲突太多,导致某一个桶(bucket)的链表过长,那么查询或者put就无限接近于O(N)复杂度。所以就想使用二叉树,因为left<root<right查询的时间复杂度是logN ,但是如果极端情况一条边特别长又会退化到O(N),所以我们就想到使用二叉平衡树,使得左右两边的高度差维持在1,但是这样的要求又太严格了,要通过不断地左旋或者右旋来维持,又会增加插入元素的时间消耗。最后我们使用了红黑树,他的叶子节点都是黑色的不存储数据,相邻的两个节点不能都是红色的,每一个节点到达叶子结点所包含的黑色节点数是相同的,根节点必须是黑色的。他的插入最多旋转两次,删除最多旋转三次,所以时间复杂度是要优于这个平衡二叉树的
  • 9.为什么不在发生hash冲突的时候就转换成红黑树呢,要等到链表长度为8才进行转换:红黑树的空间是链表的两倍,立即转换之后浪费空间,红黑树虽然查询比链表快,但是插入的时候要进行旋转和变色,所以如果链表长度小于8就转换从时间和空间上来说效果并不好。而且通过数学计算因为hash冲突导致链表长度达到8这个数字概率是很小的,所以官方才指定在8的时候才转换成红黑树。然后在节点数小于6的时候又退化成链表,因为不能一小于8就立马转换回去,这样转换太频繁了
  • 10.首先是在LinkedHashMap中使用了双向链表维护插入顺序,然后HashMap 的普通链表节点Node是没有pre指针的,是树节点TreeNode继承了LinkedHashMap.Entry从而有了pre/next双向链表,避免在Node节点中添加pre产生浪费,java的设计思想“复用优先,按需扩展”,通过双向链表就可以在树退化的时候快速转换成链表,无需重新构建节点之间的关系,包括删除某个中间节点也可以直接用过pre指针找到上一个节点,重新成链
  • 11.如果hashMap元素key没有实现comparable结构拥有比较能力,那么红黑树会使用仲裁方法进行哈希值比较和系统身份哈希值比较
  • 12.在jdk7之前,扩容的时候通常采用的是头插法,当时认为后插入的数据使用的概率更高,成为热点节点,并且不需要遍历一边到尾部,修改next指针变成新的根节点,操作简单只需要O(1)的时间复杂度,但是尾插法就需要遍历到尾部,需要O(n)的时间复杂度,但是如果两个线程同时操作这个链表,就有可能导致节点的next指针互相指向形成环,后续再遍历节点的时候就有可能陷入死循环导致CPU飙升。 所以在jdk8之后改成了尾插法。还存在其他问题比如多个线程put的时候size个数不一样,多个put可能会产生数据覆盖,当既有get又有扩容的时候可能换了桶get不到了
  • 13.hash冲突如何解决:链地址法,开放定址法(在hash数组中寻找空闲的位置来存储,线性探测一个一个往下找空闲的位置,二次探测按照平方来找),再哈希法
  • jdk7和jdk8有那些不一样:数据结构引入红黑树,插入方式变成尾插法,扩容机制从全量rehash变成看新增的那个bit位是1还是0,hash的计算:变成了一次位运算+一次异或
posted @ 2025-12-10 20:09  Huangyien  阅读(3)  评论(0)    收藏  举报