深入理解 Java HashMap 的 get() 方法及其相关实现

news2024/11/29 20:43:15

在 Java 中,HashMap 是一个非常常用的数据结构,用于存储键值对。它提供了快速的查找、插入和删除操作。HashMap 的核心功能之一是根据键获取对应的值,这主要通过 get() 方法来实现。本文将详细介绍 HashMap 的 get() 方法及其相关的辅助方法,包括 getNode()、hash()、getTreeNode()、root()、find()、comparableClassFor() 和 compareComparables()。通过这些方法的解析,您将更深入地理解 HashMap 的内部工作原理。
下面我们先来大致看一下hashMap的get()方法获取数据的大致流程,以便在看下面的源码不至于太迷茫。

在这里插入图片描述

HashMap get()方法

    //根据key获取Node
    public V get(Object key) {
        Node<K,V> e;
        //计算key的哈希码,调用getNode方法,传入哈希码和键,获取对应的Node
        //如果找到的Node为null,返回null;否则返回该Node的值
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

下面来看下hash()方法和getNode节点方法
getNode()的流程图比较复杂,我们拆分开来看
在这里插入图片描述
在这里插入图片描述

getNode()方法获取节点

获取节点大致分为四个步骤:

  • 获取桶:根据哈希值计算桶的位置,并获取该桶的第一个节点。
  • 直接匹配:检查第一个节点是否匹配,如果匹配直接返回。
  • 深入查找:
    • 如果是红黑树节点,使用红黑树的方法进行查找。
    • 如果是链表节点,遍历链表查找匹配节点。
  • 结果返回:找到匹配节点则返回该节点,否则返回 null
 //传入哈希码和key
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //检查table是否为空且长度大于0 && 使用(n-1)&hash计算索引桶(n是table长度),并获取该桶的第一个节点first
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            //检查first节点是否与给定的hash和key匹配,如果匹配,直接返回first
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //如果不匹配,检查first的下一个节点e
            if ((e = first.next) != null) {
                //如果first是treeNode(即红黑树的根节点),调用getTreeNode方法进行查找
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //否则,遍历链表,逐个检查每个节点,直到找到匹配的节点或遍历结束
                do {
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        //如果没有找到匹配的节点,返回null
        return null;
    }

hash()计算哈希

根据key计算哈希=获取key的哈希码异或上h的右移

//计算哈希
    static final int hash(Object key) {
        int h;
        //如果键为null,则返回哈希码0。HashMap允许使用null作为键,因此需要特殊处理
        //如果键不为null,则调用对象的hashCode方法获取哈希码,并将结果赋值给h
        //然后对h进行位运算h^(h>>>16)其中>>>是无符号右移操作符,将h的高位右移到低位
        //最后将哈希码h和右移后的结果进行异或操作^,得到最终的哈希码并返回
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

getTreeNode()获取树节点

检查当前节点是否有父节点,如果有父节点,说明当前节点不是根节点,调用root()方法获取根据点。
调用find方法在红黑树中查找键值对

 //红黑树获取TreeNode
    final TreeNode<K,V> getTreeNode(int h, Object k) {
        //检查当前节点是否有父节点,如果有父节点,说明当前节点不是根节点,调用root()方法获取根据点。
        //调用find方法在红黑树中查找键值对
        //find方法是一个递归方法,用于在红黑树查找指定的键值对
        return ((parent != null) ? root() : this).find(h, k, null);
    }

root方法流程图比较简单啦,一看就懂的那种
在这里插入图片描述

root()获取红黑树的根节点

获取当前节点所在的红黑树的根节点

    final TreeNode<K,V> root() {
        //使用无限循环来遍历父节点,直到找到没有父节点的节点(即根节点)
        //初始化r为当前节点this,初始化p为null
        //每次循环中,将r的父节点赋值给p
        for (TreeNode<K,V> r = this, p;;) {
            //如果p为null,说明r已经是根节点,返回r
            if ((p = r.parent) == null)
                return r;
            //否则将p赋值给r,继续循环
            r = p;
        }
    }
find()查找方法

find方法大致逻辑如下:

  • 初始化:将当前节点 p 设置为 this。
  • 遍历树:
    • 获取当前节点的左右子节点。
    • 根据哈希值和键进行比较,决定向左或向右子树搜索。
    • 如果当前节点的键与目标键匹配,返回当前节点。
    • 如果左右子节点之一为空,选择另一个子节点进行搜索。
    • 如果键实现了 Comparable 接口,根据比较结果决定搜索方向。
    • 递归地在右子树中查找,如果找到匹配节点,返回该节点。
    • 否则,继续向左子树搜索。
  • 未找到时返回:如果遍历完整棵树后仍未找到匹配节点,返回 null。
//h要查找的键的哈希码;k要查找的键的对象;kc键的可比较类(Comparable类型),用于优化比较过程
    //用于在红黑树中查找指定哈希码和键的节点
    final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
        //初始化p为当前节点this
        TreeNode<K,V> p = this;
        //do-while循环遍历树,直到找到匹配的节点或遍历结束
        do {
            int ph, dir; K pk;
            //获取当前节点p的左节点pl和右子节点pr
            TreeNode<K,V> pl = p.left, pr = p.right, q;
            //比较当前节点的哈希码ph和目标哈希码h
            //如果ph大于h,向左子树搜索(p=pl)
            if ((ph = p.hash) > h)
                p = pl;
            //如果ph小于h,向右子树搜索(p=pr)
            else if (ph < h)
                p = pr;
            //如果ph等于h,进一步比较
            //如果键相等(pk==k或k.equals(pk))返回当前节点p
            else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                return p;
            //如果左子节点为空,向右子树搜索(p=pr)
            else if (pl == null)
                p = pr;
            //如果右子节点为空,向左子树搜索(p=pl)
            else if (pr == null)
                p = pl;
            //如果键实现了Comparable接口(通过comparableClassFor方法检查)
            //使用compareComparables方法比较键,决定向左还是向右搜搜
            else if ((kc != null ||
                    (kc = comparableClassFor(k)) != null) &&
                    (dir = compareComparables(kc, k, pk)) != 0)
                p = (dir < 0) ? pl : pr;
            //如果上述条件都不满足,递归地在右子树中查找(p=pr.find(h,k,kc),如果找到返回q
            else if ((q = pr.find(h, k, kc)) != null)
                return q;
            //否则向左子树搜索(p=pl)
            else
                p = pl;
        } while (p != null);
        //如果遍历结束后没有找到匹配的节点,返回null
        return null;
    }

comparableClassFor()方法

目的:确定对象 x 是否实现了 Comparable 接口,并返回其具体的类型。
步骤:

  • 检查 x 是否是 Comparable 的实例。
  • 如果 x 是 String 类型,直接返回 String.class。
  • 获取 x 的类及其泛型接口。
  • 遍历泛型接口,检查是否是 Comparable 接口的具体实现。
  • 如果找到匹配的类型,返回该类型;否则返回 null。
  //用于确定一个对象x是否实现了Comparable接口,并且如果实现了,返回其具体的类型
    static Class<?> comparableClassFor(Object x) {
        //检查x是否是Comparable的实例
        if (x instanceof Comparable) {
            Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
            //如果x是String类型,直接返回String.class,因为String实现了Comparable<String>
            if ((c = x.getClass()) == String.class) // bypass checks
                return c;
            //获取x的类c及其泛型接口ts
            if ((ts = c.getGenericInterfaces()) != null) {
                //遍历ts数组,检查每个接口是否是ParameterizedType类型
                for (int i = 0; i < ts.length; ++i) {
                    //如果接口的原始类型(getRawType)是Comparable,并且其实际类型参数(getActualTypeArguments)是一个长度为1的数组
                    //且这个参数就是c,则返回c
                    if (((t = ts[i]) instanceof ParameterizedType) &&
                            ((p = (ParameterizedType)t).getRawType() ==
                                    Comparable.class) &&
                            (as = p.getActualTypeArguments()) != null &&
                            as.length == 1 && as[0] == c) // type arg is c
                        return c;
                }
            }
        }
        //如果没有找到匹配的类型,返回null
        return null;
    }
compareComparables()

比较两个对象 k 和 x,前提是 k 实现了 Comparable 接口,并且 x 的类型与 k 的类型相同。
步骤:

  • 检查 x 是否为空或类型不匹配,如果是,返回 0。
  • 将 k 强制转换为 Comparable 类型,并使用 compareTo 方法与 x 进行比较,返回比较结果。
//比较两个对象k和x
    static int compareComparables(Class<?> kc, Object k, Object x) {
        //如果x为null或者x的类不是kc,返回0
        //否则,将k强制转换为Comparable类型,并调用compareTo方法与x进行比较,返回比较结果
        return (x == null || x.getClass() != kc ? 0 :
                ((Comparable)k).compareTo(x));
    }

通过本文的详细解析,我们了解了 HashMap 的 get() 方法及其相关辅助方法的工作原理。get() 方法通过计算哈希码和调用 getNode() 方法来查找指定键的节点。getNode() 方法根据哈希值定位桶,并通过直接匹配或深入查找(链表或红黑树)来获取节点。hash() 方法提高了哈希分布的均匀性。getTreeNode() 和 find() 方法处理红黑树中的节点查找。comparableClassFor() 和 compareComparables() 方法则用于优化键的比较过程。
掌握这些方法的实现细节,可以帮助开发者更好地理解和使用 HashMap,并在实际开发中做出更高效的设计和优化。
更多内容请关注以下公众号
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初中数学网上考试系统的设计与实现(论文+源码)_kaic

初中数学网上考试系统的设计与实现 学生&#xff1a; 指导教师&#xff1a; 摘 要&#xff1a;科技在人类的历史长流中愈洗愈精&#xff0c;不仅包括人们日常的生活起居&#xff0c;甚至还包括了考试的变化。之前的考试需要大量的时间和精力&#xff0c;组织者还需要挑选并考查…

【大模型部署】本地运行自己的大模型--ollama

ollama简介 ollama是一款开源的、轻量级的框架&#xff0c;它可以快速在本地构建及运行大模型&#xff0c;尤其是一些目前最新开源的模型&#xff0c;如 Llama 3, Mistral, Gemma等。 官网上有大量已经开源的模型&#xff0c;部分针对性微调过的模型也可以选择到&#xff0c;…

Shell脚本linux登录自动检查

.bashrc 用于设置用户的 Bash shell 环境&#xff0c;在每次打开一个新的终端窗口或启动一个新的 Bash 会话时被执行 代码 login_check.sh #!/bin/bash clear LogFileNamepolling.$(date %F-%T) EchoFormat$(for (( i0; i<30; i )); do echo -n ""; done)# 显示…

死磕P7:JVM性能调优必知必会(二)

这是「死磕P7」系列第 008 篇文章&#xff0c;欢迎大家来跟我一起 死磕 100 天&#xff0c;争取在 2025 年来临之际&#xff0c;给自己一个交代。 接上篇&#xff0c;性能优化工具&#xff0c;尤其是图形化工具&#xff0c;绝对有 VisualVM 的一席之地&#xff0c;因为它几乎囊…

【Node.js】图片水印

上传时加水印 用户上传原始图片->服务器&#xff1a;保留原始图片以及水印图片动态水印 用户上传原始图片->服务器&#xff1a;只保留原始图片 请求图片时&#xff0c;服务器动态加水印 根据业务需求自行更改操作&#xff0c;下面只讲最简单的给图片加水印。 主要使用到…

遨游智能终端赋能“危急特”场景,力推北斗技术规模化应用!

随着《北斗规模应用三年行动计划&#xff08;2023-2025&#xff09;》的发布&#xff0c;北京、湖北、重庆等多地出台北斗支持政策&#xff0c;北斗系统正稳步迈向“安全可控&#xff0c;泛在融合&#xff0c;开放兼容&#xff0c;服务全球”的发展目标。遨游通讯紧跟国家战略步…

10/11

一、ARM课程大纲 二、ARM课程学习的目的 2.1 为了找到一个薪资水平达标的工作&#xff08;单片机岗位、驱动开发岗位&#xff09; 应用层(APP) 在用户层调用驱动层封装好的API接口&#xff0c;编写对应的API接口 ----------------------------------------------------…

怎么做接口自动化测试

在分层测试的“金字塔”模型中&#xff0c;接口测试属于第二层服务集成测试范畴。相比UI层&#xff08;主要是WEB或APP&#xff09;自动化测试而言&#xff0c;接口自动化测试收益更大&#xff0c;且容易实现&#xff0c;维护成本低&#xff0c;有着更高的投入产出比&#xff0…

2024徐州科技企业-京东(无锡)基地数字经济交流座谈会

2024年6月4日下午,2024徐州科技企业-京东(无锡)基地数字经济交流座谈会在无锡市经开区京东(无锡)数字基地成功举办,本次活动由无锡经济开发区管理委员会指导,京东科技主办,无锡经开雪浪小镇未来园区有限公司、江南大学经贸学院协办。来自徐州市的40家高新技术企业以及行业专家、…

TTM-RE: Memory-Augmented Document-Level Relation Extraction(内存增强的文档级关系提取)

摘要 文档级关系提取旨在对文档中任意两个实体之间的关联进行分类。以往的文档级关系提取方法在充分利用不同噪声水平的大量训练数据的潜力方面是无效的。例如&#xff0c;在ReDocRED基准数据集中&#xff0c;在大规模、低质量、远距离监督的训练数据上训练的最先进的方法通常…

lnmp - RBAC方案设计与实现

概述 实践的是一套企业内部使用后台OA管理系统&#xff0c;对这套系统设计的RBAC&#xff08;Role-Based Access Control&#xff0c;基于角色的访问控制&#xff09;,RBAC 方案旨在通过将后台用户与角色进行关联&#xff0c;再将角色与权限进行关联&#xff0c;实现对系统资源…

力扣之607.销售员

文章目录 1. 607.销售员1.1 题目说明1.2 准备数据1.3 解法1.4 结果截图 1. 607.销售员 1.1 题目说明 表: SalesPerson ------------------------ | Column Name | Type | ------------------------ | sales_id | int | | name | varchar | | salary | int | | commission_ra…

【CURL命令】命令行或脚本进行API测试

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 【CURL命令】命令行或脚本进行API测试 简介使用…

InfiniiVision HD3 系列示波器

_XLT_ InfiniiVision HD3 系列示波器 苏州新利通仪器仪表 使用带有定制专用集成电路 &#xff08;ASIC&#xff09; 的便携式示波器执行数字调试&#xff0c;该电路提供的垂直分辨率是其他通用示波器的四倍&#xff0c;注入噪声是其他通用示波器的一半。使用示波器进行调试&…

canvas:绘制点和点之间连线

效果图&#xff1a; <template><div class"home-box"><canvas id"canvas" /><div class"lightCircle" ref"circleRef" v-for"(item,index) in 5" :key"index"></div><div cla…

makefile与gdb的使用

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2024年10月11日 &#x1f4e8; 博主码云地址&#xff1a;渣渣C &#x1f4d5;参考书籍&#xff1a…

vue 解决高德地图Uncaught Error: Invalid Object: Pixel(NaN, NaN)

有点啰嗦&#xff0c;可以直接跳到最后看解决方法。 问题排查过程 原因起始于一个新需求&#xff1a;在编辑列表信息时需要修改设备位置。 按照文档一番操作&#xff0c;发现完美需求解决了。后续测试的时候就发现浏览器报错Uncaught Error: Invalid Object: Pixel(NaN, NaN)…

在 Notebook 中启动 FastAPI

如何在 Notebook 使用 FastAPI 对外提供 RestAPI&#xff0c;当我们测试完模型时&#xff0c;有事需要对外提供 API 进行测试。FastAPI 是 Python 中快速提供 Rest API 的框架&#xff0c;本文将对外实现一个 OCR 的图片转文字的服务。 OCR 服务 本文使用 GOT OCR 提供 OCR 识…

[Git] Git下载及使用 从入门到精通 详解(附下载链接)

前言 目录 Git概述 简介 下载 Git代码托管服务 Git常用命令 Git全局配置 获取Git仓库 在本地初始化一个Git仓库 从远程仓库克隆 基本概念 工作区文件状态 本地仓库操作 远程仓库操作 分支操作 标签操作 在IDEA中使用Git 在IDEA中配置Git 本地仓库操作 远程仓…

【unity框架开发7】对象池的使用,如何封装一个对象池管理器

文章目录 什么是对象池&#xff1f;对象池有什么用&#xff1f;对象池的原理对象池的实现1、从对象池获取对象2、回收对象3、回收所有对象4、预先往这个对象池中加载指定数量的游戏对象5、最终代码 封装对象池管理器1、对象池管理器代码2、测试调用3、生成和回收游戏对象时自动…