当前位置: 首页 > 科技观察

为什么 HashMap 会发生数据覆盖问题

时间:2023-03-13 07:18:16 科技观察

为什么HashMap会存在数据覆盖问题?转载本文请联系Java极客技术公众号。阿芬今天就来说说这个。1.7和1.8版本都存在这个问题。阿芬单独说。&扩容下生成,再看jdk1.7voidtransfer(Entry[]newTable,booleanrehash){intnewCapacity=newTable.length;for(Entrye:table){while(null!=e){Entrynext=e.next;if(rehash){e.hash=null==e.key?0:hash(e.key);}inti=indexFor(e.hash,newCapacity);e.next=newTable[i];newTable[i]=e;//线程A运行到这里就被挂起假设一开始的结构是这样的:现在有两个线程A和B,都需要进行插入操作,首先A进行插入操作,经过Hash后得到要丢弃的桶的索引坐标,运行newTable[i]=e时;到达代码行,CPU时间片用完。这时线程A停止运行,挂起。此时看起来是这样的:线程A挂起后,线程B被调度运行。巧合的是,线程B经过Hash后得到的bucketindex坐标与线程A相同,此时线程B也进行了insert手术。由于时间片充足,线程B成功将记录插入桶内:线程B插入成功后,根据Java内存模型,此时主存中存储的值就是线程B运行后的结果。然后线程A被唤醒,继续执行插入操作。对于A,前面的步骤都已经执行过了,所以不需要再运行,直接从newTable[i]=e;开始即可;代码行并继续运行。线程A保存的环境是e=12next=6e.next=newTable[i];即newTable[3]=null;,然后执行newTable[i]=e;&e=next,即newTable[3]=12e=next=6执行完成后,大概是这样的:15号元素刚刚被覆盖。jdk1.8读完1.7,我们再来看看1.8版本。数据覆盖主要发生在put操作中。以下为1.8源码(此处截取一小部分):;if((tab=table)==null||(n=tab.length)==0)n=(tab=resize()).length;if((p=tab[i=(n-1)&hash])==null)//如果没有hash碰撞,直接插入判断hash是否发生碰撞。如果不是,则不会对插入操作执行其他检查。在多线程环境下,如果线程1检查了hash,没有碰撞,那么执行插入时CPU时间片就用完了,此时挂起,线程2开始运行,不巧,此时线程2哈希后得到的值与线程1的哈希值相同,线程2插入该值,线程1恢复running,因为之前检查的Hash碰撞,此时插入时不再检查,直接插入值,那么线程2插入的值会被覆盖。HashMap中数据覆盖问题的主要原因是没有加锁。线程环境会出现数据覆盖问题。解决问题。面试官,你能不能别再问我HashMap了?有小伙伴留言,发现阿芬的思路不对,跟泊松分布有关。这一点在源代码中也有解释:理想情况下,在underrandomhashCodes中,bin中节点的频率遵循泊松分布,对于默认调整大小阈值0.75,参数平均约为0.5,尽管由于ocrtheresizingvariabilityof粒度.listIgare(exp(-0.5)*pow(0.5,k)/factorial(k)).Thefirstvaluesare:0:0.606530661:0.303265332:0.075816333:0.012636064:0.001579525:0.000157956:0.000013167:0.000000948:0.00000006more:lessthan1intenmillion从从源码可以看出,在负载因子为0.75(HashMap默认)的情况下,单个hash槽中元素个数为8的概率为0.00000006,这是一个相当小的值,所以7为作为分水岭,等于7不转换,大于等于8转红黑树,小于等于6转链表高低16位?有小伙伴问阿粉,hash搅动跟高低16位有关系吗?粉丝们不知道这个关系是怎样的关系,但是作者一定考虑过数字16的选择。散列的搅动主要发生在resize()方法中。我们可以看源码注意:Initializesordoublestablesize。如果为null,则根据初始容量targetholdinfieldthreshold分配s。否则,因为我们使用二次幂展开,所以每个bin中的元素必须保持相同的索引,或者在新表中以两次偏移的幂移动。翻译过来就是:在初始化或者增加表大小时,那么如果没有指定容量,那么如果指定了,由于使用了2的幂,所以每个bin元素也必须保持相同的索引,或者在中移动2的幂新表,所以它似乎与16位有关