目录

一、HashMap基础结构

二、put操作流程分析

put操作关键步骤总结

三、get操作流程分析

get操作关键步骤总结

四、延伸

1.hash()方法

2. 扩容

resize()方法的主要逻辑:

Java 8中对扩容的优化:

3. 转向红黑树的条件


HashMap作为Java集合框架中最重要且最常用的数据结构之一,其内部实现机制值得深入探究。本文将从源码角度分析HashMap的get和put操作流程,基于Java 8的实现进行讲解。

一、HashMap基础结构

在讲解get和put之前,我们先了解HashMap的核心结构:

// HashMap的主要成员变量
transient Node<K,V>[] table; // 存储元素的数组
transient int size;         // 实际存储的键值对数量
int threshold;              // 扩容阈值 (capacity * loadFactor)
final float loadFactor;     // 负载因子(默认0.75)

HashMap采用"数组+链表+红黑树"的混合存储结构:

  • 数组(table)是主体

  • 当哈希冲突时,使用链表法解决

  • 当链表长度超过8且数组长度≥64时,链表转为红黑树

二、put操作流程分析

put操作是HashMap的核心功能之一,其中主要方法为:

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

核心逻辑putVal方法:

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:table为空则初始化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:节点key已存在,直接覆盖valueif (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);// 步骤5:链表处理else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 链表长度≥8转为红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}// key已存在则跳出循环if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 步骤6:写入valueif (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// 步骤7:超过阈值则扩容++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

put操作关键步骤总结

  1. 计算哈希值:通过hash()方法计算key的哈希值

  2. 初始化检查:如果table为空,则调用resize()初始化

  3. 定位桶位置:通过(n-1) & hash计算数组下标(等价于 hash % n,但效率更高--位运算比取模快

  4. 处理冲突

    • 如果该位置为空,直接插入新节点

    • 如果key已存在,则覆盖value

    • 如果是树节点,调用红黑树的插入方法

    • 如果是链表,遍历链表并在尾部插入

  5. 树化检查:链表长度≥8时可能转为红黑树

  6. 扩容检查:size超过threshold则扩容

三、get操作流程分析

首先来看get的实现方法:

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

其核心实现getNode方法解析:

final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 步骤1:table不为空且长度>0且对应位置有节点if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 步骤2:检查第一个节点if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 步骤3:遍历后续节点if ((e = first.next) != null) {// 步骤4:如果是树节点if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 步骤5:遍历链表do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}

get操作关键步骤总结

  1. 计算哈希值:与put相同的hash()方法

  2. 定位桶位置:同样的(n-1) & hash计算方式

  3. 检查首节点:先比较hash,再比较key

  4. 处理冲突

    • 如果是树节点,调用红黑树的查找方法

    • 如果是链表,遍历查找

  5. 返回结果:找到返回value,未找到返回null

四、延伸

1.hash()方法

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

可以看见代码中将高16位与低16位异或,这么做可以实现:

  • 减少哈希冲突

  • 充分利用高位的哈希信息

  • 使哈希分布更加均匀

2. 扩容

resize()方法的主要逻辑:

  1. 计算新容量和新阈值

  2. 创建新数组

  3. 将旧数组元素重新分配到新数组(rehash)

Java 8中对扩容的优化:

  • 无需重新计算hash,通过(e.hash & oldCap) == 0判断位置

  • 元素要么在原位置,要么在原位置+oldCap的位置

3. 转向红黑树的条件

链表转为红黑树的两个条件:

  1. 链表长度 ≥ TREEIFY_THRESHOLD(8)

  2. 数组长度 ≥ MIN_TREEIFY_CAPACITY(64)

【注】上面表示默认情况下是链表长度-8,且数组长度-64

否则会优先进行扩容而不是转向红黑树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88192.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88192.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88192.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识Neo4j之图数据库(二)

目录 一、图数据库如何工作 二、为什么使用图数据库 Neo4j 图数据库以节点、关系和属性的形式存储数据&#xff0c;而不是用表或文档进行数据存储。这意味着用户可以像在白板上画草图那样来组织数据。而且&#xff0c;由于图数据库不受限于预先定义的数据模型&#xff0c;因此…

Python 中 ffmpeg-python 库的详细使用

文章目录 一、ffmpeg-python库概述1.1 ffmpeg-python库介绍1.2 安装1.3 优势1.4 常用场景二、基本使用2.1 视频信息获取2.2 视频转码三、视频处理3.1 视频裁剪3.2 视频缩放3.3 视频旋转四、音频处理4.1 提取音频4.2 音频混合五、高级使用5.1 添加水印5.2 视频滤镜5.3 视频合成5…

JAVA策略模式demo【设计模式系列】

策略模式用在统一的入口&#xff0c;但需要根据某个类型判断后续执行逻辑&#xff0c;例如我最近遇到的场景&#xff1a;我需要对接一个设备&#xff0c;前端请求我这边&#xff0c;我再去和设备交互&#xff0c;但设备种类很多&#xff0c;各自有自己的接入规则&#xff01;传…

mysql索引:索引应该选择哪种数据结构 B+树 MySQL中的页 页主体 页目录 索引分类

索引是什么?为什么要使用索引? 以前学数据结构时学了ArrayList,我们可以往里面存放数据 但是有问题,也就是说当程序重启或是电脑关机之后,数据就没有了,为什么? 因为他的数据是保存在内存中的 数据库把数据保存在磁盘中,就可以完成对数据的持久化内存与外存的区别 内存&…

SpringBoot静态资源与缓存配置全解析

springboot中静态资源classpath就是resource文件夹下欢迎页规则项目启动默认去找静态资源下的index.html页面 默认访问该页面favicon原则在静态资源目录下寻找favicon.ico缓存实验在请求中使用Cache-Control 时&#xff0c;它可选的值有&#xff1a;在响应中使用Cache-Control …

基于 Python Django 和 Spark 的电力能耗数据分析系统设计与实现7000字论文实现

摘要随着能源问题日益突出&#xff0c;电力能耗数据分析对于提高能源利用效率、降低能源消耗具有重要意义。本文设计并实现了一个基于 Python Django 和 Spark 的电力能耗数据分析系统。系统采用前后端分离架构&#xff0c;前端使用 Django 框架实现用户界面&#xff0c;后端使…

elementUI vue2 前端表格table数据导出(二)

为啥前端导出不在赘述了&#xff0c;不然读者也难看到这篇文章。第一步&#xff1a;安装依赖npm install vue-json-excel第二步&#xff1a;引用依赖配置// 导出Excel文件组件 import JsonExcel from vue-json-excel; Vue.component(downloadExcel, JsonExcel)第三步&#xff1…

RabbitMQ 4.1.1-Local random exchange体验

Local Random Exchange 一种 RabbitMQ 4.0 引入的新型交换机&#xff0c;主要是为 request-reply&#xff08;RPC&#xff09;场景 设计的。 使用这种交换机时&#xff0c;消息只会被路由到本地节点上的队列&#xff0c;可以确保极低的消息发布延迟。如果有多个本地队列绑定到该…

中山排气歧管批量自动化智能化3D尺寸测量及cav检测分析

当前制造业快速发展&#xff0c;传统测量方法正面临严峻挑战。生产规模的持续扩张使得现有测量手段逐渐暴露出效率不足的问题&#xff0c;这种技术滞后性正在直接影响企业的整体生产效率。具体表现为测量速度跟不上生产节拍&#xff0c;精度要求难以达标&#xff0c;最终导致生…

Debian 11 Bullseye 在线安装docker

首先移除所有错误的 Docker 软件源&#xff1a;sudo rm -f /etc/apt/sources.list.d/docker*安装必要依赖sudo apt update sudo apt install -y ca-certificates curl gnupg添加 Docker 官方 GPG 密钥&#xff08;使用国内镜像&#xff09;&#xff1a;curl -fsSL https://mirr…

Spring Boot 项目中多数据源配置使用场景

在 Spring Boot 中配置多数据源是一个非常常见的需求&#xff0c;主要用于以下场景&#xff1a; 读写分离&#xff1a;一个主数据库&#xff08;Master&#xff09;负责写操作&#xff0c;一个或多个从数据库&#xff08;Slave&#xff09;负责读操作&#xff0c;以提高性能和可…

FAAC 在海思平台使用得到aac实时音频流

FAAC 在海思平台使用得到aac实时音频流 使用 FAAC将音频 pcm转为 aac 主要参见这篇博客 FAAC 在君正平台使用得到aac实时音频流_君正 x2600 音频-CSDN博客

javascript函数参数类似python函数参数星号*解耦数组

序言通常情况下&#xff0c;我们很可能不清楚参数有多少&#xff0c;这个时候用的都是数组。但是使用数组和单个元素&#xff0c;从内心情感来说&#xff0c;它们是两种维度&#xff0c;为了让参数成为一个数组&#xff0c;把单个输入的参数强加一个数组的外壳&#xff0c;并不…

C语言基础(1)

1.编译器的选择 我们的c语言是一门&#xff0c;我们写的c语言代码是文本文件(存放在.c为后缀的文件中)&#xff0c;文本文件本身无法被执行&#xff0c;必须通过编译器的编译和链接器的链接&#xff0c;生成可执行的二进制文件&#xff0c;才能够被执行注意&#xff1a; 每个源…

Rust赋能美团云原生DevOps实践

Rust 云原生 DevOps 实践 在云原生环境中,Rust 的高性能与安全性使其成为构建微服务和基础设施工具的理想选择。Docker 作为容器化标准工具,结合 Rust 的跨平台特性,可高效实现持续集成与部署(CI/CD)。 构建优化的 Rust Docker 镜像 多阶段构建是 Rust 项目容器化的关键…

计算机网络实验——配置ACL

ACL基础一、实验目的1. 配置H3C路由器基本ACL。二、实验要求1. 熟练掌握网络配置能力。2. 熟练掌握ACL基本配置。三、实验步骤&#xff08;1&#xff09;使用reset saved-configuration命令和reboot命令&#xff0c;重置路由器原有配置&#xff0c;如图1所示。图 1&#xff08;…

在本地部署mcp服务器实现自然语言操作mysql数据库,轻松实现数据表的增~ 删~ 改~ 查~

1.将写好的mcp_server代码放在本地任意盘&#xff01; import asyncio import logging import os import sys from mysql.connector import connect, Error from mcp.server import Server from mcp.types import Resource, Tool, TextContent from pydantic import AnyUrl# Co…

2025快手创作者中心发布视频python实现

难度还行&#xff0c;只有一个__NS_sig3加密&#xff0c;流程麻烦点cookies_list cookie.split("; ")cookie_dict {}# 遍历每个 Cookie&#xff0c;根据等号将键值对拆分并添加到字典中for cookie in cookies_list:key_value cookie.split("")if len(ke…

Android 组件内核

文章目录什么是binder1. 什么是Binder&#xff1f;2. Binder架构组成3. 工作原理与通信流程1&#xff09;服务注册2&#xff09;服务查询3&#xff09;通信过程4&#xff09;核心数据结构4. 关键技术点5. 常见面试考点1&#xff09;Binder与传统IPC&#xff08;Socket、管道、共…

java类加载机制:Tomcat的类加载机制

Tomcat类加载机制深度解析&#xff1a;打破双亲委派的Web容器实现 Tomcat作为Java Web容器&#xff0c;其类加载机制为满足Web应用的隔离性、热部署和兼容性需求&#xff0c;对标准Java类加载机制进行了定制化扩展&#xff0c;核心是打破双亲委派模型并引入多层级类加载器。以下…