HashMap的原理,1.7为什么会形成环

HashMap的结构

HashMap是由数组+链表的方式存放数据的,jdk1.8则是数组+链表+红黑树来存放数据的

HashMap的put流程

1、首先调动hash方法,计算put元素的key值

此处计算hash值的算法并非只是调用了object的hashCode方法,而是调用了Object.hashCode后又对hash值进行了右移16位 取异或,也就是进行了再散列,这样减少了下表冲突的可能

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

 2、存放元素

1、当第一次存放元素时,数组长度为0,则需要进行数组扩容操作  resize()方法

2、当后续存放元素时,先获取数组下标,在拿取对应数组下标的值

获取下标的方法是     (n - 1) & hash  n为数组长度 ,hash为计算出的hash值

当对应下标取出的值的key和存放元素的key值相等时,直接替换原来值的value

当取出值的key不等于存放的key时,则判断取出来的值的类型,是treenode还是node,treenode则走红黑树查找对应key的方法,node就是普通列表的方法 去对比key

3、若存放的元素以前不存在即不是修改值操作,当找到对应存放的链表位置时,先判断该列表长度是否大于8,如果链表长度大于8则调用链表转红黑树的操作,在调用红黑树方法时又进行了数组长度的判断,若数组长度小于64,则不转红黑树,直接追加node对象

 4、上述步骤后即下图,list.size++,判断是否大于hashmap的阈值,阈值算法为最大容量*负载因子    (默认最大容量初始值是16,负载因子是3/4)也就是当集合元素超过12时会进行一次扩容

 5、扩容操作

都是在原最大容量,原阈值进行左移1位也就是扩容1倍大小

创建新的数组,数组长度为最大容量,老的数组进行数据迁移,复制到新的数组中去,复制逻辑就是一个个循环,插入到新数组中,具体算数组下标的方法也运用了位运算,什么高位,低位,我也没搞懂

HashMap  1.7为什么会导致死循环

1.7的transfer方法,这里采用的头插法,即新加的元素在链表的头部,这样减少了遍历获取尾节点的性能消耗,但多线程并发插入同一个元素的情况,由于是头插法,所以再设置为头节点的时候,另一个线程该节点已经设置成功头节点了,那当前线程这个头节点的下一节点指向的就也是自己的引用了,这样的链表当调用map.get的时候,需要遍历链表数据的时候,由于下一节点一直指向的是自己,导致死循环

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    //判断是否有超出扩容的最大值,如果达到最大值则不进行扩容操作
    if (oldCapacity == MAXIMUM_CAPACITY) {
      threshold = Integer.MAX_VALUE;
      return;
    }
 
    Entry[] newTable = new Entry[newCapacity];
    // transfer()方法把原数组中的值放到新数组中
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    //设置hashmap扩容后为新的数组引用
    table = newTable;
    //设置hashmap扩容新的阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  }

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
        	//1, 获取旧表的下一个元素
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //通过key值的hash值和新数组的大小算出在当前数组中的存放位置
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

HashMap  1.8会导致死循环吗?

会,只是导致的原因和1.7不同,1.8插入元素采用的尾插法,所以只会存在覆盖掉尾节点的值,并不会导致循环引用问题。

导致死循环的地方是  当链表转换红黑树时,或者操作树时会导致死循环

为什么1.8会采用尾插法呢

由于1.7没有使用红黑树,如果用尾插法每次都要遍历链表去获取尾节点,很耗费性能,但1.8采用了红黑树结构,并且红黑树转换条件是链表长度大于8时转换,所以遍历链表的次数最大就是8,当大于8就转成红黑树了,遍历次数为树的深度,所以遍历的性能消耗可以忽略,从而也解决了头插法循环引用的问题

为什么红黑树的转换阈值是8,不是6呢

hashmap转红黑树的阈值为8_一文弄懂面试必考的HashMap_weixin_39522927的博客-CSDN博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[3\]提到,HashMap1.7版本中没有使用红黑树,而是使用了链表来存储元素。由于使用尾插法,每次都要遍历链表去获取尾节点,这样耗费性能。而在1.8版本中,HashMap采用了红黑树结构,并且转换条件是链表长度大于8时才转换成红黑树。这样,遍历链表的次数最大就是8,当超过8时就转换成红黑树。遍历红黑树的次数取决于树的深度,所以遍历的性能消耗可以忽略不计。这样就解决了头插法循引用的问题。\[3\] 所以,在HashMap 1.7版本中,由于没有使用红黑树,可能存在的问题。但在1.8版本中,通过使用红黑树结构,并设置了转换阈值为8,可以有效地解决的问题。 #### 引用[.reference_title] - *1* [HashMap1.7 扩容产生链表](https://blog.csdn.net/weixin_42496727/article/details/124118191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [HashMap原理1.7为什么形成](https://blog.csdn.net/weixin_41796956/article/details/120562231)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值