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

HashMap(JDK1.7、JDK1.8)原理与结构分析与synchronizedMap()

目录

一、HashMap。

jdk1.7。

jdk1.8。

6个核心常量。(控制底层结构)

hash计算(二次hash)。下标计算。(hash&(容量-1))

初始容量。链表与红黑树间转换条件。

扩容机制。(核心)

二、线程安全的HashMap?(synchronizedMap)


一、HashMap。

  • jdk1.7。
  • JDK1.7:底层是数组 + 链表(Entry 数组)。


  • 可以看源码(之前是Entry对象)Node对象。(我看的这个是jdk21,也就是jdk1.8后)

  • jdk1.8。
  • JDK1.8:底层是数组 + 链表 + 红黑树(Node 数组。链表长度达标后转为红黑树)。

  • 6个核心常量。(控制底层结构)
  • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  • 设定 HashMap 底层数组默认初始容量为16,且容量必须为 2 的幂

  • static final int MAXIMUM_CAPACITY = 1 << 30;
  • 限制 HashMap 底层数组最大容量为2³⁰。受 int 类型存储范围制约,避免容量过大超出合理范围。

  • static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 默认负载因子大小。当已存元素数超过 “当前数组容量 × 负载因子” 时,触发数组扩容,平衡空间与查询效率。

  • static final int TREEIFY_THRESHOLD = 8;
  • 链表转红黑树的阈值。当链表元素数多于 8 时,将链表转换为红黑树,优化查询性能(红黑树查询复杂度更低 )。

  • static final int UNTREEIFY_THRESHOLD = 6;
  • 红黑树转链表的阈值。当红黑树中元素数少于 6 时,红黑树转回链表,因元素少时长链表查询也较高效,且转换成本低。
  • static final int MIN_TREEIFY_CAPACITY = 64;
  • 最小树形化数组容量。若数组(桶)数量小于 64,即便链表元素数超 8,也先扩容而非转红黑树。因小容量数组扩容成本低,能减少哈希冲突 。
  • hash计算(二次hash)。下标计算。(hash&(容量-1))
  • hash计算的过程直接看源码。
  • HashMap不会直接使用key对象的 hashCode() 返回值作为哈希值!而是对其进行了二次哈希处理,这样做的目的是让哈希值更加均匀地分布,减少哈希冲突。
  • 首先判断key是否为null,如果key是null,直接返回哈希值:0
  • 如果key不为null,获取key的 hashCode() 值赋值给变量h,然后将h的高 16 位与低 16 位进行异或(^)操作,得到最终的哈希值


  • 初始容量。链表与红黑树间转换条件。
  • 数组初始容量默认是16,但如果通过构造方法 new HashMap(int initialCapacity) 指定初始大小时,HashMap会自动将其调整为最近的2的幂次方(如:指定10,实际初始容量是16)。
  • 数组的容量必须是2的幂次方!才能通过:key的hash值&(容量-1) 计算下标(等价于取模运算,但效率更高)。

  • 链表转红黑树触发条件(JDK1.8):

    1. 链表长度 > 8(即第 9 个元素插入时);
    2. 数组容量 > 64
    3. 若数组容量≤64,即使链表长度 > 8,也会先优先扩容数组,而非转红黑树。(因为小容量数组扩容成本更低,且能减少冲突)。
    4. 红黑树转链表:当红黑树节点数 ≤6 时,会退化为链表(避免频繁转换的性能消耗)。
    5. 红黑树查找、插入、删除时间复杂度均为 O (logn),优于长链表的 O (n)

  • 扩容机制。(核心)
  • 扩容(resize)是当元素数量(size)超过 容量 × 负载因子(默认 0.75) 时触发,新容量为原容量的 2 倍(保证仍是 2 的幂次方)。

  • jdk1.7
  1. 需要重新计算哈希(rehash)。对每个节点的 key 重新计算 hash 值,再通过 hash&(新数组容量-1) 得到新下标。
  2. 链表插入顺序:采用头插法(新节点插入链表头部),可能导致多线程扩容时出现链表环(死循环)。

  • jdk1.8
  • 无需重新计算 hash。利用容量是 2 的幂次方的特性,通过 原hash值&原数组容量 判断节点位置:
    1. 结果为 0:下标不变。
    2. 结果非 0:下标 = 原下标 + 原容量。
  • 链表插入顺序:采用尾插法(保持原链表顺序),避免了 JDK1.7 的链表环问题。
  • 红黑树处理:扩容时会将红黑树拆分为两个链表(或红黑树),分别放入新数组的两个位置。

二、线程安全的HashMap?(synchronizedMap)

  • HashMap 本身是非线程安全的。
  • Java中可以利用 java.util.Collections 类的 synchronizedMap 方法,将 HashMap 包装成线程安全的映射集合。


  • 简单demo测试。
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;public class Test {public static void main(String[] args) {HashMap<String,Object> hashMap = new HashMap();//使用Collections.synchronizedMap()方法 创建一个同步的HashMapMap<String, Object> syncMap = Collections.synchronizedMap(hashMap);//模拟多线程操作new Thread(()->{syncMap.put("key1","1");System.out.println("线程1写入值...");},"线程1").start();new Thread(()->{Object key1 = syncMap.get("key1");System.out.println("线程2获取值:"+key1);},"线程2").start();}
}


  • synchronizedMap 内部通过包装原Map,对几乎所有修改和访问方法(如:put、get、remove 等),都使用了同一把内置锁(this 锁,即包装后的 syncMap 实例作为锁对象)进行同步。保证多线程操作时同一时刻只有一个线程能执行这些方法,从而避免并发问题。
http://www.xdnf.cn/news/16389.html

相关文章:

  • 【LeetCode刷题指南】--队列实现栈,栈实现队列
  • C 语言详解:特性、应用与发展
  • GRE和MGRE综合实验
  • DMDSC安装部署教程
  • 基于cooragent的旅游多智能体的MCP组件安装与其开发
  • Android Jetpack 组件库 ->Jetpack Navigation (下)
  • 从治理到共情——平台伦理的乡村共建之路
  • 在 C# 中,问号 ? 的一些作用
  • HTML初学者第五天
  • 启动式service
  • 强化学习(第三课第三周)
  • 在 Scintilla 中为 Squirrel 语言设置语法解析器的方法
  • Kubernetes 配置管理
  • odoo代码分析(一)
  • 认识泛型、泛型类和泛型接口
  • 大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(2):LangChain + LlamaIndex 实现
  • Java学习第七十七部分——JVM运行时数据区
  • Java同步锁性能优化:15个高效实践与深度解析
  • 7月26号打卡
  • C++/CLI与标准C++的语法差异(一)
  • ASP.NET Core MVC中taghelper的ModelExpression详解
  • Spring Boot 3 如何整合 MinIO 实现分布式文件存储?
  • MyBatis-Plus 通用 Service 详解:IService 与 CRUD 操作全解析
  • PYTHON从入门到实践-15数据可视化
  • 【资讯】2025年软件行业发展趋势:AI驱动变革,云原生与安全成核心
  • PHP框架之Laravel框架教程:1. laravel搭建
  • 亚马逊测评采购:如何打造安全的环境,技术基础关键
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 70(题目+回答)
  • Avantage6.6下载与安装教程
  • 差模干扰 共模干扰