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

HashMap工作原理

HashMap 是 Java 集合框架中最重要且最常用的数据结构之一,它基于哈希表实现了 Map 接口,提供了高效的键值对存储和检索能力。
Java 8 之后的 HashMap 采用 数组 + 链表 + 红黑树 的混合结构:

// 简化结构示意
transient Node<K,V>[] table;  // 主数组(哈希桶数组)static class Node<K,V> {      // 链表节点final int hash;final K key;V value;Node<K,V> next;
}static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {  // 红黑树节点TreeNode<K,V> parent;  TreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // 保持双向链表特性boolean red;
}

一、工作流程

1. 存储过程(put)

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}

具体步骤:
计算哈希值:对 key 进行 hash(key) 计算(高16位异或低16位)
确定桶位置:index = (n - 1) & hash(n 是数组长度)
处理碰撞:
如果桶为空:直接创建新节点插入
如果桶不为空:
如果是链表:遍历查找,存在则更新,不存在则尾插(Java8),检查是否需树化(链表长度≥8)
如果是红黑树:按照树的方式插入
扩容检查:如果元素总数超过阈值(容量×负载因子),进行扩容(2倍)

2. 读取过程(get)

public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}

具体步骤:
计算 key 的哈希值
确定桶位置 (n-1) & hash
在桶中查找:
如果是链表:顺序查找(O(n))
如果是红黑树:树查找(O(log n))

3. 扩容机制(resize)

当 size > threshold(容量×负载因子)时触发:

创建新数组(2倍于原容量)
重新计算所有元素的哈希位置(rehash)
迁移元素:
Java 7:头插法(可能导致死链)
Java 8+:保持原链表顺序或拆分树

二、线程安全问题

HashMap 不是线程安全的,多线程环境下可能出现:

数据不一致
Java 7 扩容时的死循环问题(已修复)
解决方案:
使用 Collections.synchronizedMap
使用 ConcurrentHashMap(推荐)

三、常见问题

1、HashMap的value是否可以传null?

HashMap允许null作为值存储在HashMap中。允许一个null作为key,如果尝试使用多个null作为key,后面的会覆盖前面的。

2、HashMap和Hashtable的区别

HashMap线程不安全,Hashtable线程安全
HashMap允许null键值,Hashtable不允许
HashMap性能更好

3、HashMap 的初始容量和负载因子是什么?

默认初始容量 16
默认负载因子 0.75
当元素数量超过(容量×负载因子)时扩容

4、HashMap 如何解决哈希冲突?

哈希冲突是指不同的键(key)经过哈希函数计算后得到相同的哈希值,从而映射到哈希表的同一个位置。HashMap 主要采用以下几种方式解决哈希冲突:

  1. 链地址法(拉链法)Java 7 及之前版本的实现方式:
    使用数组+链表的结构
    当发生哈希冲突时,将冲突的键值对以链表形式存储在同一个桶(bucket)中
    新元素插入到链表头部(头插法)
// Java 7 的简单示意结构
数组 + 链表:
[0] -> null
[1] -> Entry<K,V> -> Entry<K,V> -> null  // 哈希冲突的键值对形成链表
[2] -> null
...
  1. 红黑树优化(Java 8+)
    当链表长度超过阈值(默认为8)时,将链表转换为红黑树
    当红黑树节点数小于阈值(默认为6)时,转换回链表
    这种改进将最坏情况下的时间复杂度从O(n)降低到O(log n)
// Java 8+ 的混合结构
[0] -> null
[1] -> TreeNode<K,V>  // 转换为红黑树
[2] -> Node<K,V> -> Node<K,V> -> null  // 仍然是链表
...

5、为什么 HashMap 的长度是 2 的幂次方?

方便通过位运算计算索引:(n-1) & hash
提高计算效率,减少哈希冲突

6、HashMap 的 put 方法执行流程?

  • 计算 key 的 hash 值
  • 计算数组下标
  • 判断是否冲突
  • 插入节点(链表或红黑树)

7、为什么 Java 8 要将链表转为红黑树?

防止哈希碰撞攻击
链表过长时查询效率从 O(n) 提升到 O(logn)

8、HashMap 为什么不是线程安全的?

多线程扩容可能导致死循环
使用 ConcurrentHashMap 替代

9、HashMap 的扩容机制是怎样的?

扩容为原大小的 2 倍
重新计算所有元素的位置

10、如何优化 HashMap 的性能?

设置合理的初始容量
选择合适的负载因子
使用不可变对象作为键

11、HashMap 和 TreeMap 的区别?

HashMap 基于哈希表,无序
TreeMap 基于红黑树,有序

12、HashMap 在多线程环境下会出现什么问题?

数据不一致
死循环(Java 7 及以前版本)
推荐使用 ConcurrentHashMap

13、如何设计一个好的 hashCode 方法?

保证相同对象返回相同值
尽量使不同对象返回不同值
计算简单高效

14、HashMap 的 key 为什么通常用不可变对象?

防止 key 变化导致 hash 值变化
保证数据一致性

15、如何实现一个线程安全的 HashMap?

使用 Collections.synchronizedMap
使用 ConcurrentHashMap
使用读写锁封装

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

相关文章:

  • 使用Tomcat Clustering和Redis Session Manager实现Session共享
  • 设备树下的LED驱动实验
  • 【机器人】2025年人形机器人时代:伦理迷雾中的人类界限
  • PAT 1072 Gas Station
  • visionpro获取电脑cpu序列号
  • 生信分析自学攻略 | R语言数据类型和数据结构
  • 矿物分类系统开发笔记(二):模型训练[删除空缺行]
  • leetcode2248. 多个数组求交集
  • ES支持哪些数据类型,和MySQL之间的映射关系是怎么样的?
  • Vue3 学习教程,从入门到精通,vue3综合案例:“豪华版”待办事项(41)
  • [Polly智能维护网络] 网络重试原理 | 弹性策略
  • PyTorch数据处理工具箱(utils.data简介)
  • UE5 PCG 笔记(一)
  • C++ STL(标准模板库)学习
  • 华为鸿蒙系统SSH如何通过私钥连接登录
  • 传统概率信息检索模型:理论基础、演进与局限
  • 短剧小程序系统开发:打造沉浸式短剧观影体验
  • EPM240T100I5N Altera FPGA MAX II CPLD
  • Spring Cache 整合 Redis 实现高效缓存
  • idea如何设置tab为4个空格
  • 复习登录校验流程:会话跟踪技术与请求拦截方案详解
  • SpringBoot-集成POI和EasyExecl
  • 《Light Sci Appl》突破:vdW材料实现亚波长光学涡旋生成,转换效率达46%
  • 前端基础知识操作系统系列 - 01(操作系统的理解?核心概念有哪些)
  • Spring Ai Prompts
  • 佰力博检测与您探讨电晕极化时有时会击穿是什么原因
  • 海洋牧场智能化监控系统升级,保障养殖安全
  • Web3.0 时代的电商系统:区块链如何解决信任与溯源问题?
  • 嵌入式系统学习Day19(数据结构)
  • 用poll改写select