HashMap -- 调研

news2025/3/12 17:38:22

HashMap 调研

      • 前言
        • JDK1.8之前
          • 拉链法:
        • JDK1.8之后
        • JDK1.7 VS JDK1.8 比较
          • 优化了一下问题:
      • HashMap的put方法的具体流程?
      • HashMap的扩容resize操作怎么实现的?

前言

在Java中,保存数据有两种比较简单的数据结构:数组和链表。

数组的特点是:寻址容易,插入和删除 困难;
链表的特点是:寻址困难,但插入和删除容易;

所以我们将数组和链表结合在一起,发挥两者各 自的优势,使用一种叫做拉链法的方式可以解决哈希冲突


JDK1.8之前

JDK1.8之前采用的是拉链法。

拉链法:
   将链表和数组相结合。也就是说创建一个链表数组,

数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

在这里插入图片描述

JDK1.8之后

相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将
链表转化为红黑树,以减少搜索时间。

在这里插入图片描述


JDK1.7 VS JDK1.8 比较
优化了一下问题:
  1. resize 扩容优化
  2. 引入了红黑树,目的是避免单条链表过长而影响查询效率
  3. 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。

HashMap的put方法的具体流程?

在这里插入图片描述

public class HashMapDemo<K, V> extends HashMap<K, V> {

    // 默认初始容量 - 必须是 2 的幂。
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    // 如果任一带有参数的构造函数隐式指定更高的值,则使用最大容量。必须是 2 的幂 <= 1<<30。
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 初始因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 使用树而不是箱列表的箱计数阈值。当将元素添加到至少具有这么多节点的 bin 时,bin 会转换为树。该值必须大于2,并且至少应为8,以便与树木移除中有关收缩后转换回普通箱的假设相吻合。
    static final int TREEIFY_THRESHOLD = 8;


    // 在调整大小操作期间对(分割)bin 进行树形化的 bin 计数阈值。应小于 TREEIFY_THRESHOLD,且最多 6 个网格,以便在移除时进行收缩检测。
    static final int UNTREEIFY_THRESHOLD = 6;


    // bin 可以树化的最小表容量。  初始数量(否则,如果 bin 中的节点太多,则表的大小将被调整。)应至少为 4的倍数以避免调整大小和树化阈值之间的冲突。
    static final int MIN_TREEIFY_CAPACITY = 64;


    transient HashMapDemo.Node<K, V>[] table;
    transient Set<Entry<K, V>> entrySet;
    transient int size;
    transient int modCount;
    int threshold;


    // 创建一个节点Node类  作为链表使用
    static class Node<K, V> implements Map.Entry<K, V> {
        // hash值
        final int hash;
        // key
        final K key;
        // 对应的值
        V value;
        // 子节点
        HashMapDemo.Node<K, V> next;

        Node(int hash, K key, V value, HashMapDemo.Node<K, V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        // get set
        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final String toString() {
            return key + "=" + value;
        }

        // 计算规则,  节点上的key 进行hashCode 计算,异或 hashCode 值
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }


    // 定义一个 hash 方法, 通过hashCode 得到一个长度 位运算 h>>> 高低16bit
    static final int hash(Object key) {
        int h;

        // key.hashCode()) ^ (h >>> 16)   hashcode  和 自己hashcode 位运算异或
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


    // put方法
    @Override
    public V put(K key, V value) {
        return super.put(key, value);
    }


    V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {

        HashMapDemo.Node<K, V>[] tab;


        HashMapDemo.Node<K, V> p;
        int n;
        int i;

        // 初始化表大小或将表大小加倍。如果为空,则根据阈值字段中保存的初始容量目标进行分配
        if ((tab = table) == null || (n = tab.length) == 0) {
            // 步骤1:tab为空则创建   table未初始化或者长度为0,进行扩容
            n = (tab = resize()).length;
        }


        // 计算下标是否为空  通过hash算法  得到 p (n - 1) & hash 确定元素存放在哪个桶中
        if ((p = tab[i = (n - 1) & hash]) == null) {
            // 为空找不到,放到桶里面
            tab[i] = newNode(hash, key, value, null);
        }


        // 桶里面存在类
        else {
            HashMapDemo.Node<K, V> e;
            K k;

            // 步骤3:节点key存在,直接覆盖value比较桶中第一个元素(数组中的结点)的hash值相等,key相等
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
                // 直接覆盖值
                e = p;
            }


            // 步骤4:判断该链为红黑树  hash值不相等,即key不相等;为红黑树结点 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node   e可能为空
            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) {
                        // 在链表最末插入Node结点
                        p.next = newNode(hash, key, value, null);

                        判断链表的长度是否达到转化红黑树的临界值,临界值为8
                        // TREEIFY_THRESHOLD 属性 8 前面定义类
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            // 链表结构转树形结构
                            treeifyBin(tab, hash);
                        break;
                    }


                    // 判断链表中结点的key值与插入的元素的key值是否相等
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                        // 相等,跳出循环
                        break;
                    }

                    // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                    p = e;
                }
            }


            // 判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的val ue这个值
            if (e != null) {
                // 记录e的value
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null) {
                    // 用新值替换旧值
                    e.value = value;
                }
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }

        // 结构性修改
        ++modCount;
        // 步骤6:超过最大容量就扩容 实际大小大于阈值则扩容
        if (++size > threshold) {
            // 插入后回调
            resize();
        }

        // node 节点插入
        afterNodeInsertion(evict);
        return null;
    }
}
  1. 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

  2. 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向6,如果table[i]不为空,转向3;

  3. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashCode以及equals;

  4. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值 对,否则转向5;

  5. 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

  6. 插入成功后,判断实际存在的键值对数量size是否超多了 大容量threshold,如果超过,进行扩容。


HashMap的扩容resize操作怎么实现的?

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

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

相关文章

Java实现防重复提交,使用自定义注解的方式

目录 1.背景 2.思路 3.实现 创建自定义注解 编写拦截器 4.使用 5.验证 6.总结 1.背景 在进行添加操作时&#xff0c;防止恶意点击&#xff0c;后端进行请求接口的防重复提交 2.思路 通过拦截器搭配自定义注解的方式进行实现&#xff0c;拦截器拦截请求&#xff0c;使…

如何在 Keras 中开发具有注意力的编码器-解码器模型

link 【翻译自 &#xff1a; How to Develop an Encoder-Decoder Model with Attention in Keras 】 【说明&#xff1a;Jason Brownlee PhD大神的文章个人很喜欢&#xff0c;所以闲暇时间里会做一点翻译和学习实践的工作&#xff0c;这里是相应工作的实践记录&#xff0c;…

大数据Doris(八):启动FE步骤

文章目录 启动FE步骤 一、配置环境变量 二、​​​​​​​创建doris-mate

变分自动编码器 (VAE)02/2 PyTorch 教程

一、说明 在自动编码器中&#xff0c;来自输入数据的信息被映射到固定的潜在表示中。当我们旨在训练模型以生成确定性预测时&#xff0c;这特别有用。相比之下&#xff0c;变分自动编码器&#xff08;VAE&#xff09;将输入数据转换为变分表示向量&#xff08;顾名思义&#xf…

气象台卫星监测vr交互教学增强学生的学习兴趣和动力

对地观测是以地球为研究对象&#xff0c;依托卫星、飞船等光电仪器&#xff0c;进行各种探测活动&#xff0c;其核心是遥感技术&#xff0c;因此为了让遥感专业学员能提前熟悉对地观测规则、流程、方法及注意事项&#xff0c;借助VR虚拟现实制作的三维仿真场景&#xff0c;能让…

全新彩虹商城时光模板知识付费系统源码+内有5000多商品+易支付源码

源码简介&#xff1a; 全新彩虹商城时光模板知识付费系统源码&#xff0c;这是最新的彩虹知识付费商城系统&#xff0c;具备众多强大且实用的功能。首先&#xff0c;它支持二级分类和多级分销&#xff0c;使得商品分类更为清晰&#xff0c;销售网络更具扩展性。 其次&#xf…

机器人轨迹规划算法的研究现状

近年来&#xff0c;随着机器人技术的迅速发展&#xff0c;机器人在工业、医疗、军事等领域的应用越来越广泛。机器人轨迹规划是机器人控制的重要环节之一&#xff0c;它决定了机器人在执行任务时的运动轨迹&#xff0c;直接影响机器人的精度、速度和稳定性。因此&#xff0c;机…

【PCIE720】基于PCIe总线架构的高性能计算(HPC)硬件加速卡

PCIE720是一款基于PCI Express总线架构的高性能计算&#xff08;HPC&#xff09;硬件加速卡&#xff0c;板卡采用Xilinx的高性能28nm 7系列FPGA作为运算节点&#xff0c;在资源、接口以及时钟的优化&#xff0c;为高性能计算提供卓越的硬件加速性能。板卡一共具有5个FPGA处理节…

代码混淆界面介绍

代码混淆界面介绍 代码混淆功能包括oc&#xff0c;swift&#xff0c;类和函数设置区域。其他flutter&#xff0c;混合开发的最终都会转未oc活着swift的的二进制&#xff0c;所以没有其他语言的设置。 代码混淆功能分顶部的显示控制区域&#xff1a;显示方式&#xff0c;风险等…

python 深度学习 解决遇到的报错问题6

目录 一、解决报错HTTPSConnectionPool(hosthuggingface.co, port443): Max retries exceeded with url: /bert-base-uncased/resolve/main/vocab.txt (Caused by ConnectTimeoutError(, Connection to huggingface.co 如何从huggingface官网下载模型 二、nx.draw if cf._ax…

jupyter 切换虚拟环境

当前只有两个环kernel 我已经创建了很多虚拟环境&#xff0c;如何在notebook中使用这些虚拟环境呢&#xff1f;请看下面 比如说我要添加nlp 这个虚拟环境到notebook中 1. 切换到nlp环境 2. 安装如下模块 pip install ipykernel 3. 执行如下命令 python -m ipykernel install …

VS2019如何显示和去除控制台页面

这是控制台页面&#xff1a; 方法&#xff1a; 选中目标项目&#xff0c;右键--->属性--->配置属性--->链接器--->系统--->子系统--->(窗口/控制台)

地级市HVV | 未授权访问合集

在网站前后端分离盛行下&#xff0c;将大部分权限控制交给前端&#xff0c;导致js中隐藏未授权或者可绕过的前端鉴权。前后端分离的好处是提高开发效率&#xff0c;同时防止黑客更直接的对服务器造成危害&#xff0c;但权限控制的工作量全部交给前端会导致大量页面未授权或者后…

面试经典 150 题 1 —(双指针)— 125. 验证回文串

125. 验证回文串 方法一 class Solution { public:bool isPalindrome(string s) {string newStr "";for(int fast 0; fast < s.size(); fast){if(isalnum(s[fast]))){newStr tolower(s[fast]);}}string tmp newStr;reverse(tmp.begin(), tmp.end());if(strcm…

【计算机网络】TCP协议与UDP协议详解

文章目录 一、传输层 1、1 再次理解传输层 1、2 再次理解端口号 1、2、1 端口号范围划分 1、2、2 认识知名端口号 1、3 网络常用指令netstat 与 pidof 二、UDP协议 2、1 UDP协议的报文 2、2 UDP的特点 2、3 UDP的缓冲区 三、TCP协议 3、1 TCP协议的报文 3、2 确认应答 3、3 按…

计算机毕业设计选什么题目好?springboot 个人健康信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

基于SpringBoot的抗疫物资管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户管理 公告信息管理 轮播图管理 物质分类管理 物质信息管理 物质入库管理 物质出库管理 个人信息 前台首页功能实现 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着现在网络的快速发展&#xff0c;网…

简单制作RT-Thread Studio的CH32V303的BSP支持包

简单制作RT-Thread Studio的CH32V303的BSP支持包 开原仓库链接在此&#xff1a;RTT_Studio_BSP_CH32V303 参考 CH32V307V-R1&#xff08;V1.0.8&#xff09;的 BSP&#xff0c;更新了外设驱动库之类的。 可以在 RT-Thread SDK 管理器中导入离线资源包&#xff0c;可以新建 RT…

CSS学习基础知识

CSS学习笔记 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,…

Linux Centos7 下使用yum安装的nginx平滑升级

1. 查看当前nginx版本 1nginx -v2. 查看centos版本 1cat /etc/redhat-release3. 创建一个新的文件nginx.repo&#xff0c;其中第三行的7是因为我的centos版本是7点多的&#xff0c;你看自己是多少就改多少 1vim /etc/yum.repos.d/nginx.repo23[nginx]4namenginx repo 5baseu…