HashMap详解

概述

HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在,即主要用来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。它的 key、value 都可以为 null,此外,HashMap 中的映射不是有序的。
默认初始16个大小,扩容*2 负载因子是0.75

HashMap1.8 和1.7的区别

JDK1.7 Hashmap 底层数据结构数组+链表
JDK1.8 Hashmap 底层数据结构数组+链表+红黑树

HashMap死循环只发生在JDK1.7版本中,主要原因是JDK1.7中的HashMap,在头插法+链表 +多线程并发 + 扩容这几个情形累加到一起就会形成死循环。多线程环境下建议采用
ConcurrentHashMap替代。在JDK1.8中,HashMap改成了尾插法,解决了链表死循环的问题。

HashMap1.7 版本中,底层是基于数组+链表实现的,如果发生Hash 冲突概率问题,会存放到同一个链表中,链表如果过长 会从头查询到尾部效率非常低
  HashMap1.8 版本 (数组容量>=64&链表长度大于8)就会将该链表转化红黑树。
如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。(JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法)(注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)jdk1.7 数组+链表 链表缺陷:如果链表过长的情况下
查询的时间复杂度就是为 o(n) 需要从头查询尾部 效率非常低
JDK1.8 数组+链表+红黑树

这样设计的好处是,使得锁的粒度相比Segment来说更小了,发生hash冲突和加锁的频率也降低了,在并发场景下的操作性能也提高了。而且,当数据量比较大的时候,查询性能也得到了很大的提升

HashMap 根据 Key 查询时间复杂度
1.Key 没有产生 hash 冲突 时间复杂度是为 o(1); 只需要查询一次
2.Key 产生 hash 冲突 采用链表存放则为 O(N) 从头查询到尾部
3.key 产生 hash 冲突采用红黑树存放则为 O(LogN)

为什么要用红黑树,为什么不用二叉树,为什么不用平衡二叉树,为什么不用二叉树

红黑树一种平衡二叉树,其插入、删除、查找最坏时间复杂度都为O(logn),
避免了二叉树最坏情况下 O(n)时间复杂度
为什么不用平衡二叉树
平衡二叉树比红黑树更严格平衡树,为了保持保持平衡,需要旋转次数更多,也就说平衡二叉树保持平衡效率更低,所以平衡二叉树插入和删除效率比红黑树要低。

为什么在 1.8 中链表大于 8 时会转红黑树

红黑树平均查找长度log(n),如果长度为 8,平均查找长度为log(8)=3,
链表平均查找长度为 n/2,当长度为 8 时,平均查找长度为 8/2=4,这才有转换成树必要;链表长度如果小于等于 6,6/2=3,而 log(6)=2.6,虽然速度也很快,但转化为树结构和生成树时间并不会短。
解决冲突用的是链表,为什么Redis用头插,而HashMap用的是尾插,
HashMap用尾插,是因为在并发的场景下,可能会导致链表的死链。
但是Redis是单线程执行,所以不会有并发导致死链,头插法比尾插速度要快很多。所以用头插法即可。

HashMap如何避免内存泄漏问题

自定义对象作为key的时候,重写equals方法和hashcode方法,确保key不重复创建。
如果两个对象,new的是同一个类,内容相同,但是没有重写equals和hashcode方法,比较结果是不相等的,在HashMap 中存储的时候会导致内存堆积而造成内存泄漏问题
原文链接:https://blog.csdn.net/T_karine/article/details/128303480

HashMap如何存放1万条key效率最高

正常存放1万个key的情况下大概扩容10次。多次扩容严重影响性能,所以当存放1万条key时,需要提前对数组大小进行初始化。

HashMap Key为null存放在什么位置

key可以为null,存放在index=0的位置。

HashMap扩容和初始化

默认初始16个大小(必须是2的次方),当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16 * 0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,具体
它等于负载因子 乘以 容量大小,负载因子的默认值是0.75,而容量大小默认是16,。也就是说,第1次扩容的动作会在元素个数达到12的时候触发,扩容的大小是原来的2倍。HashMap的最大容量是Integer.MAX_VALUE也就是2的31次方减1

负载因子是0.75
负载因子表示Hash表中的元素填充程度。负载因子的值越大,也就意味着触发扩容的元素个数就越多。虽然,它的整体空间利用率会比较高,但是Hash冲突的概率也会增加。那么,反之,负载因子的值越小,那么触发扩容元素的个数也就越少,也就意味着Hash冲突的概率也会减少。但是,对于内存空间的浪费自然就比较多了,而且还会增加扩容的频率。
因此,扩容因子的值的设置,本质上就是一个冲突的概率以及空间利用率之间的一个平衡。关于0.75这个值的来源,和统计学里面的泊松分布有关系。
我们知道,HashMap采用的是链式寻址的方式来解决Hash冲突的问题。而为了避免链表过长,导致时间复杂度增加的情况,所以,HashMap判断链表长度大于等于8的时候,就会转换为红黑树,从而提升检索的效率。当负载因子为0.75的时候,链表长度达到8的可能性几乎为0,也就是说,比较好的做到了空间成本和时间成本的平衡。

如果使用 Object 作为 HashMap 的 Key,应该怎么办呢?

重写 hashCode()和 equals()方法
重写 hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中
排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的 Hash 碰撞;
重写 equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非 null 的引
用值 x,x.equals(null)必须返回 false 的这几个特性,目的是为了保证 key 在哈希表中的唯 一性;

参考
文章

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值