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

微信开放api安徽seo推广公司

微信开放api,安徽seo推广公司,wordpress微信公众号推送,wordpress国内视频网站吗常见集合篇(五):深入解析 HashMap,从原理到源码,全方位解读 常见集合篇(五):深入解析 HashMap,从原理到源码,全方位解读引言一、HashMap 的实现原理1.1 数据结构基础1.2 存储逻辑示例 二、HashMap 的 put 方…

常见集合篇(五):深入解析 HashMap,从原理到源码,全方位解读

  • 常见集合篇(五):深入解析 HashMap,从原理到源码,全方位解读
    • 引言
    • 一、HashMap 的实现原理
      • 1.1 数据结构基础
      • 1.2 存储逻辑示例
    • 二、HashMap 的 `put` 方法具体流程
      • 2.1 JDK 8 源码分析
      • 2.2 执行步骤拆解
    • 三、HashMap 常见属性
    • 四、HashMap 的扩容机制
      • 4.1 触发条件
      • 4.2 扩容过程
      • 4.3 示例
    • 五、HashMap 的寻址算法
      • 5.1 哈希计算
      • 5.2 桶定位
    • 六、HashMap 在 1.7 下的多线程死循环问题
      • 6.1 问题根源
      • 6.2 示例场景
    • 七、HashSet 与 HashMap 的区别
    • 八、HashTable 与 HashMap 的区别
    • 总结

常见集合篇(五):深入解析 HashMap,从原理到源码,全方位解读

引言

在 Java 编程领域,HashMap 是一种高频使用的数据结构,无论是日常开发中的键值对存储,还是面试中的核心考点,它都占据着重要地位。本文将围绕 HashMap 的实现原理、put 方法流程、常见属性、扩容机制、寻址算法等核心问题展开深度剖析,同时对比 HashSet、HashTable 的差异,帮助读者全面掌握这一关键数据结构。


一、HashMap 的实现原理

1.1 数据结构基础

HashMap 采用 “数组 + 链表 + 红黑树” 的复合结构:

  • 数组:作为底层存储,每个数组元素称为一个“桶”(Bucket)。

  • 链表:当多个键值对的哈希值映射到同一桶时,通过链表解决冲突。

  • 红黑树:当链表长度超过阈值(默认 8),链表会转为红黑树,提升查询效率(JDK 8 新增优化)。

  • 冲突处理:由于不同的键可能具有相同的哈希值,这就会导致冲突。当发生冲突时,HashMap使用链表或红黑树等数据结构来存储具有相同哈希值的键值对。这些数据结构允许在冲突的位置上存储多个键值对,并通过比较键的equals()方法来区分它们

    链表:在JDK 8之前,HashMap使用链表来解决冲突。当多个键值对被映射到同一个桶时,它们会形成一个链表。通过遍历链表来查找、插入或删除键值对。但是,当链表长度过长时,会影响HashMap的性能

    红黑树:从JDK 8开始,当链表长度超过一个阈值(默认为8)时,链表会被自动转换为红黑树,以提高操作效率。红黑树的查找、插入和删除操作具有较低的时间复杂度,可以在平均情况下保持对数时间复杂度

1.2 存储逻辑示例

假设存储键值对 {“key1”, “value1”}

  1. 计算 key1 的哈希值,确定其在数组中的桶位置。
  2. 若桶为空,直接将键值对存入桶对应的链表头。
  3. 若桶已存在元素,遍历链表(或红黑树),对比键是否相等:
    • 相等则覆盖值;
    • 不相等则新增节点(若链表长度达标,转换为红黑树)。

二、HashMap 的 put 方法具体流程

2.1 JDK 8 源码分析

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 1. 初始化数组if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 2. 定位桶,若桶空则新建节点if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;// 3. 桶首节点匹配if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))e = p;// 4. 红黑树处理else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 5. 遍历链表for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 链表转红黑树if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash);break;}// 找到重复键if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // 覆盖值V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;return oldValue;}}++modCount;// 6. 检查扩容if (++size > threshold)resize();return null;
}

2.2 执行步骤拆解

  1. 计算哈希:通过 hash(key) 对键进行哈希处理,减少哈希冲突。
  2. 初始化数组:若数组未初始化,调用 resize() 创建默认容量(16)的数组。
  3. 定位桶位置:通过 (n - 1) & hash 计算桶索引。
  4. 处理冲突
    • 桶为空:直接插入新节点。
    • 桶非空:检查桶首节点是否匹配,匹配则覆盖值;若为红黑树,调用红黑树插入方法;否则遍历链表,插入新节点(若链表过长,转换为红黑树)。
  5. 检查扩容:若元素数量超过阈值,触发扩容。

三、HashMap 常见属性

属性名称类型默认值说明
DEFAULT_INITIAL_CAPACITYint16默认初始容量,必须是 2 的幂次。
DEFAULT_LOAD_FACTORfloat0.75f默认负载因子,用于计算扩容阈值。
thresholdint-扩容阈值,值为 容量 × 负载因子
loadFactorfloat-负载因子,控制扩容时机,影响空间和时间效率。
modCountint-记录集合结构修改次数,用于 ConcurrentModificationException 快速失败机制。

四、HashMap 的扩容机制

4.1 触发条件

size(当前元素数量)超过 threshold(扩容阈值,即 容量 × 负载因子)时,触发扩容。

4.2 扩容过程

  1. 创建新数组:新数组容量为旧数组的 2 倍,例如旧容量 16,新容量 32。
  2. 迁移元素:遍历旧数组每个桶,重新计算元素在新数组的位置并复制。由于容量是 2 的幂次,元素新位置要么在原位置,要么在原位置 + 旧容量。

4.3 示例

假设初始容量为 16,负载因子 0.75,阈值为 12。当添加第 13 个元素时:

  1. 触发扩容,创建容量为 32 的新数组。
  2. 遍历旧数组,每个元素重新计算哈希:
    • 若元素原桶索引为 i,新索引可能是 ii + 16
    • 例如,旧数组中某元素哈希值与 15(16-1)按位与得 5,扩容后与 31(32-1)按位与,若高位变化,新索引为 5 + 16 = 21。

五、HashMap 的寻址算法

5.1 哈希计算

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 目的:将哈希码的高 16 位与低 16 位异或,减少哈希冲突。
  • 示例:若 key.hashCode()0b10101100,右移 16 位后与原值异或,混合高低位信息。

5.2 桶定位

通过 (n - 1) & hash 计算桶索引,其中 n 是数组长度(且为 2 的幂次)。例如,数组长度 16(二进制 10000),n - 11501111),与哈希值按位与,确保结果在数组范围内,且分布均匀。


六、HashMap 在 1.7 下的多线程死循环问题

6.1 问题根源

JDK 1.7 中,HashMap 扩容采用头插法。多线程环境下,若线程 A 和线程 B 同时扩容,可能导致链表节点顺序混乱,形成循环链表。

6.2 示例场景

  1. 初始链表:节点 AB,存储在旧数组桶中。
  2. 线程 A 扩容:复制 B 到新数组,再复制 A,新链表为 AB
  3. 线程 B 同时扩容:同样复制节点,但因并发操作,链表可能变为 ABA,形成循环。
  4. 查询触发死循环:遍历链表时,因循环结构导致无限循环。

七、HashSet 与 HashMap 的区别

对比维度HashSetHashMap
存储内容仅存储键,值为固定对象 PRESENT存储键值对
实现原理基于 HashMap,复用 HashMap 的键存储逻辑独立实现键值对存储
核心方法add()remove() 等键操作put()get() 等键值对操作
空值支持允许存储一个 null允许 null 键和 null 值(键唯一)

示例

HashSet<String> set = new HashSet<>();
set.add("test"); // 内部调用 HashMap 的 put(key, PRESENT)HashMap<String, Integer> map = new HashMap<>();
map.put("key", 1); // 存储键值对

八、HashTable 与 HashMap 的区别

对比维度HashTableHashMap
线程安全方法加 synchronized,线程安全非线程安全
空值支持键和值均不能为 null允许 null 键和 null 值(键唯一)
性能同步开销大,性能较低无同步开销,性能更优
继承体系继承 Dictionary实现 Map 接口
扩容机制扩容为原容量 2 倍 + 1扩容为原容量 2 倍

示例

HashTable<String, Integer> table = new HashTable<>();
// table.put(null, 1); // 编译报错,不允许 null 键
// table.get(null); // 编译报错,不允许 null 键HashMap<String, Integer> map = new HashMap<>();
map.put(null, 1); // 允许 null 键
map.put("key", null); // 允许 null 值

总结

通过对 HashMap 实现原理、put 流程、属性、扩容机制、寻址算法的深入分析,以及与 HashSet、HashTable 的对比,我们全面掌握了这一数据结构的核心要点。在实际开发中,需根据场景选择合适的集合:单线程环境优先用 HashMap;需要线程安全时,可选择同步包装类 Collections.synchronizedMapConcurrentHashMap;而 HashTable 因性能问题已逐渐被替代。理解这些细节,不仅能写出更高效的代码,也能在面试中从容应对相关问题。

http://www.dtcms.com/wzjs/389203.html

相关文章:

  • pw网站更换域名网络营销讲师
  • 怎么做刷qq会员网站2018加快百度收录的方法
  • 地方网站成本苹果看国外新闻的app
  • 上海网站建设微信开发58百度搜索引擎
  • 织梦后台怎么换网站模板nba新闻最新消息滚动
  • 网站建设文化包括哪些seo入门教学
  • 成都市建设网站玉林网站seo
  • 怎样建立网站目录结构关键词点击优化工具
  • 做调查的网站营销策划公司取名大全
  • 中国疫情防控措施爱站工具seo综合查询
  • 中企动力天津分公司小吴seo博客
  • 做视频网站应该选什么服务器网络营销所学课程
  • 手机版文章网站源码口碑营销属于什么营销
  • 做网站 设备友链价格
  • wordpress 列表页输出培训机构优化
  • 网站建设百度文库国内搜索引擎大全
  • wordpress文章表情重庆百度seo
  • 鹤壁做网站的联系方法百度百科搜索入口
  • html网站开发实例网络营销推广方式包括哪几种
  • 外贸网站推广 雅虎问答有用吗精准引流推广
  • 开个网站做英语培训微信投放广告多少钱
  • 网站排名的英文北京seo推广
  • 全屏网站大小网站策划书案例
  • 小型网站设计及建设毕业论文互联网营销师怎么报名
  • iis 默认网站 删除关键词优化方法
  • 深圳华强北商业圈太原seo网站排名
  • 做俄语网站建设厦门百度seo
  • 网站中在线咨询怎么做南宁网络推广品牌
  • php实现网站tag标签数据推广公司
  • 用discuz做的网站网站注册信息查询