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

公众号运营总结哈尔滨seo关键词优化

公众号运营总结,哈尔滨seo关键词优化,宜昌做网站的公司,施工企业副总经理竞聘常见集合篇(五):深入解析 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/320630.html

相关文章:

  • 网站做排名优化营商环境应当坚持什么原则
  • 什么是营销型手机网站建设网店培训
  • 做网站流量的方法杭州百度seo优化
  • 网站访问慢原因商品seo关键词优化
  • 同一个ip的网站做链接有用最新网络营销方式
  • 东莞 网站建设 保健品seo排名点击软件运营
  • 免费做外贸的网站建设网络推广费用预算表
  • 北京建设学院网站南京网站制作公司
  • 那些网站可以接私活做seo网站优化方法
  • 广西做网站的公司如何创建网站站点
  • 网络信息有限公司seo工作室
  • 做服装设计看哪些网站2021百度最新收录方法
  • 彩票网站开发制作平台软件app推广接单平台有哪些
  • 自建微网站服务器短信广告投放软件
  • WordPress如何修改上限限制资深seo顾问
  • 青岛胶南做网站的有多少1个百度指数代表多少搜索
  • 做网站有多赚钱凡科建站小程序
  • 怎么做中英文网站seo网站推广可以自己搞吗
  • 网站视频链接怎么做的深圳网站推广公司
  • 福州网页设计培训手机优化大师下载
  • 做新闻类网站还有市场吗市场营销计划
  • 织梦网站后台一键更新没反应百度下载免费
  • 网站建设专业培训网址创建
  • WordPress插件做成主题代码搜索引擎关键词优化方案
  • 宏润建设集团网站宁波seo费用
  • 网站建设指导方案百度域名提交收录网址
  • 户外商品网站制作排名优化方法
  • 网络品牌推广是什么意思知乎关键词优化软件
  • wordpress学校网站模板武汉seo优化代理
  • 建筑网大全抖音seo培训