前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HashMap的为啥用尾插法?

HashMap的为啥用尾插法?

作者头像
名字是乱打的
发布2021-12-22 15:31:19
1.7K0
发布2021-12-22 15:31:19
举报
文章被收录于专栏:软件工程软件工程
一.那么关于遇到hash冲突时候这个数据是头插呢?还是尾插呢?

关于HashMap链表插入问题,java8之前之前是头插法

头插法:就是说新来的值会取代原有的值,原有的值就顺推到链表中去,就像上面的例子一样,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。

java8之后,都是所用尾部插入了

java8 后为啥改为尾部插入呢?

解决上面的问题需要一些预备知识

hashmap的扩容原理

hashmap扩容分为两步

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

为什么要重新Hash呢,不直接复制过去呢? 因为长度扩大以后,Hash的规则也随之改变。 Hash的公式---> index = HashCode(Key) & (Length - 1) 原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了,之前的所有数据的hash值得到的位置都需要变化。 如下图: 扩容前:

扩容后:

java8 后为啥改为尾部插入呢?

主要是为了安全,防止环化

因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

就可能出现下面的情况,大家发现问题没有?

B的下一个指针指向了A

一旦几个线程都调整完成,就可能出现环形链表

如果这个时候去取值,就出现了无限循环的状态..

使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了

Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。

那是不是意味着Java8就可以把HashMap用在多线程中呢? 那是必然不可以的,HashMap put/get方法都没有加同步锁,这里存在一个并发修改的问题,ConcurrentModifyExcption,所以线程安全还是无法保证。

二 .那我问你HashMap的默认初始化长度是多少?16,为啥是16呢?

为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 这个算法应该如何设计呢? 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:取余操作中如果除数是2的幂次则等价于与其除数减一的与操作 (也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且采用二进制位操作,相对子取余操作能够提高运算效率,这就解释了HashMap的长度为什么是2的幕次方。 详细请看

至于为啥初始长度是16我觉得就是大家都觉得16通常情况够用了吧.如果你有其他看法可以评论

三 .为啥我们重写equals方法的时候需要重写hashCode方法呢?

因为在java中,所有的对象都是继承于Object类。

Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样

对于值对象,==比较的是两个对象的值

对于引用对象,比较的是两个对象的地址

所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

不然一个链表的对象,你哪里知道你要找的是哪个,到时候发现hashCode都一样,这不是完犊子嘛。

关于本文中头插法尾插法详情可看码农届网红敖丙的原文https://juejin.im/user/59b416065188257e671b670a/posts 但是我觉得这篇文章写的比较简略,面向面试还行,如果要了解基本原理建议大家可以看看https://blog.csdn.net/thqtzq/article/details/90485663

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/10/19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.那么关于遇到hash冲突时候这个数据是头插呢?还是尾插呢?
  • hashmap的扩容原理
    • Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
    • 二 .那我问你HashMap的默认初始化长度是多少?16,为啥是16呢?
    • 三 .为啥我们重写equals方法的时候需要重写hashCode方法呢?
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档