当前位置: 首页 > news >正文

【HashMap全面知识点】— 快速理解HashMap

目录

一、核心概念

1.定义

2.核心参数

二、底层数据结构与演进

结构细节:

三、核心算法与实现

1.哈希值计算

2.索引计算

3.put方法流程(JDK 1.8)

4.扩容(resize)机制

四、JDK 1.7 与 JDK 1.8 核心差异

五、并发隐患与解决方案

1.并发问题

2.解决方案

六、高频面试题

1.HashMap与Hashtable的区别?

2.为什么容量必须是2的幂?

3.HashMap中Key可以是任何对象吗?

4.HashMap迭代器的fail-fast机制?

5.红黑树转换条件?

七、实践建议

一、核心概念

1.定义

实现Map接口,用于存储键值对(key-value),允许null键(仅一个)和null值,非线程安全,元素无序(插入顺序与遍历顺序不一致)

2.核心参数

  • capacity:哈希桶数组容量(默认16,必须为2的幂,最大2³⁰)
  • LoadFactor:负载因子(默认0.75),衡量数组填充程度(填充程度=实际元素数量(size)/数组容量(capacity))
  • threshold:扩容阈值(capacity*LoadFactor),元素数量超过此值时触发扩容
  • size:实际存储的键值对数量

举例:

        当数组容量为 16,负载因子 0.75 时,扩容阈值 = 16 × 0.75 = 12

        当元素数量达到 12 时,填充程度 = 12/16 = 0.75,此时触发扩容(容量变为 32),避免填充程度过高导致性能下降

二、底层数据结构与演进

版本数据结构红黑树支持链表插入方式
JDK 1.7数组+单向链表不支持头插法
JDK 1.8数组+单向链表+红黑树支持尾插法

结构细节:

  • 哈希桶数组(table):存储节点的数组,每个元素是链表或红黑树的头结点
  • 链表:解决哈希冲突,相同索引的元素以链表形式存储
  • 红黑树:JDK 1.8 新增,当链表长度>=8且数组容量>=64时,链表转为红黑树查询(时间复杂度从O(n)优化为O(logn));当节点数<=6时,红黑树转为链表

三、核心算法与实现

1.哈希值计算

JDK 1.8 对hashCode()进行二次处理,增强随机性

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 高16位与低16位异或
}

2.索引计算

通过哈希值与数组长度减一的与运算确定索引

int index = (table.length - 1) & hash; // 等价于 hash % table.length(仅当长度为2的幂时)

数组长度为2的幂保证table.length - 1二进制全为1,使索引分布更均匀

3.put方法流程(JDK 1.8)

  • 计算 key 的哈希值。
  • 若数组为空,初始化数组(触发 resize())。
  • 根据索引定位位置:
    • 位置为空:直接插入新节点。
    • 位置非空:
      • 若 key 已存在(哈希值和 equals 均匹配),覆盖旧值。
      • 若为红黑树节点,调用树插入方法。
      • 若为链表节点,遍历链表插入,长度达标则转红黑树。
  • 元素数量超过阈值时触发扩容

4.扩容(resize)机制

  • 触发条件: size > threshold
  • JDK 1.7:新容量 = 原容量 * 2,重新计算所有元素的哈希值和索引,头插法迁移节点
  • JDK 1.8:新容量 = 原容量 * 2,通过哈希值高位判断索引(无需重算哈希),尾插法迁移节点,红黑树可能拆分

四、JDK 1.7 与 JDK 1.8 核心差异

特性JDK 1.7JDK 1.8
数据结构数组 + 链表数组 + 链表 + 红黑树
链表插入方式头插法(容易成环)尾插法(避免环)
哈希计算直接使用hashCode()二次哈希(高16位异或低16位)
扩容索引计算重新计算哈希值利用高位判断(优化性能)
冲突查询效率O(n)(链表)O(logn)(红黑树)

JDK 1.7 中,HashMap 扩容时会将原数组中的链表节点迁移到新数组,采用头插法(新节点插入到链表头部)。在多线程并发扩容时,可能出现链表成环,进而导致死循环。

JDK 1.8 改用尾插法(新节点插入到链表尾部),迁移节点时保持原链表的顺序,从根本上避免了环的产生。

五、并发隐患与解决方案

1.并发问题

  • JDK 1.7:扩容时头插法可能导致链表成环,引发死循环;多线程put可能导致数据丢失
  • JDK 1.8:尾插法避免了环问题,但仍存在数据覆盖(如果同时插入相同的key)和size计数不准的问题

2.解决方案

  • 使用Collections.synchronizedMap(new HashMap<>())(全局加锁,效率低)
  • 使用ConcurrentHashMap(JDK 1.8 采用CAS + 局部锁,效率高)

六、高频面试题

1.HashMap与Hashtable的区别?

区别HashMapHashtable
线程安全非线程安全安全(方法加synchronized)
null值允许null键值不允许
容量初始容量16(扩容*2)初始容量11(扩容*2+1)

2.为什么容量必须是2的幂?

保证(n-1) & hash 能充分利用哈希值的低位,减少冲突;扩容时可通过高位快速计算新索引

3.HashMap中Key可以是任何对象吗?

可以,但是需要重写hashCode()和equals()方法

(key为什么要重写hashCode()和equals()?)

  • hashCode() 保证相同对象返回相同哈希值,equals()保证逻辑相同的对象被视为同一key
  • 若不重写,可能导致相同逻辑的key被视为不同的键,或不同的key被误判为相同的键

4.HashMap迭代器的fail-fast机制?

迭代过程中若结构被修改(如put/remove),会抛出ConcurrentModificationException,通过modCount变量实现(每次修改递增,迭代时校验)

5.红黑树转换条件?

  • 链表转红黑树:链表长度 >= 8 且数组长度 >= 64
  • 红黑树转链表:节点数 <= 6

6.HashMap和TreeMap的使用场景?

  • HashMap:追求查询/插入效率,无需排序
  • TreeMap:需要键有序(自然排序或自定义排序),查询效率略低

7.JDK 1.8对HashMap的优化?

  • 引入红黑树,优化链表长查询性能
  • 扩容时使用尾插法避免链表成环
  • 计算新索引是通过高位判断,无需重新计算哈希值

七、实践建议

  • 初始容量设置:已知元素数量时,设为(int)(expectedSize / 0.75 + 1),避免频繁扩容
  • key选择:优先使用不可变对象(如String、Integer),避免哈希值变化导致查找失败
  • 遍历方式:推荐entrySet()(同时获取键值对,效率高于keySet())

如果您觉得这篇文章对您有帮助,请点赞关注,我会持续分享更多实用的技术文章。如有任何问题,欢迎在评论区留言讨论。

http://www.dtcms.com/a/479716.html

相关文章:

  • 【系统分析师】写作框架:面向对象设计方法及其应用
  • 图书网站建设实训总结人像摄影网站十大排名
  • 网站开发常问的技术性问题哈尔滨建站模板厂家
  • 国内信息图制作网站有哪些网站开发的技术支撑 经验能力
  • 上海缔客网站建设公司婚纱摄影网站
  • 湖北省建设厅官方网站网页传奇游戏哪个好
  • 河南省建设工程标准定额管理网站福建seo搜索引擎优化
  • 北京公司可以在上海建网站吗成都高端网页设计公司
  • 网站流行趋势怎么开发手机app
  • JavaScript学习笔记(三十):JS优雅降级与渐进增强实战指南
  • GESP等级认证C++三级16-位运算5-1
  • 一个人做网站时间品牌注册怎么注册
  • 网站建设 后台建站快车凡科
  • 2.4运算符
  • 张店网站制作哪家好自己建网站做外贸
  • 新的网站建设技术方案cnzz wordpress
  • Latex 写作注意事项
  • 公司手机网站模板joomla 网站图标
  • 凡科网可以免费做网站吗建立免费空间网站
  • ​​ 算法知识图谱:终结碎片化学习的终极指南
  • 哪个不是网站开发工具安溪人做的网站
  • 虚拟机安装小皮面板
  • 东营做营销型网站建设WordPress 收款方案
  • Jupyterlab pip 无法安装到当前kernel对应环境下
  • 内网网站开发费用网片的重量计算公式
  • 江苏水利工程建设局网站旺店通app手机企业版下载
  • 响应式网站建设 苏州wordpress的选页插件
  • 网站管理与建设试题沉默是金歌词谐音对照
  • 关于在ant-design-vue 3.x a-modal中使用tinymce-vue全屏时,工具栏下拉列表不展示问题
  • nas上建设网站莱州市招聘网站