Java集合之HashMap

概述

HashMap是基于哈希表的Map接口实现的一种存储key、value的数据结构,提供了所有可选的映射操作,且键值允许null的存在,不保证数据映射的顺序,也不能保证顺序在一段时间内保持不变

底层结构

jdk1.7:数组+链表

jdk1.8:数组+链表+红黑树

哈希冲突

从上文图中显而易见,hashMap用的拉链法来解决哈希冲突的,也就是当一个数据计算出来的hash值已经在table数组中存在的话,就以链表的结构追加在后面

  1. 当链表长度>=8且数组长度>=64时,会把链表转化为红黑树
  2. 当链表长度>=8,当数组长度<64时,会优先进行扩容,而不是转化为红黑树
  3. 当红黑树节点数<=6时,自动转化为链表

源码解析

put()

put流程

根据插入数据的key进行hash计算,得到在table数组将要插入的下标索引i,若i号下标为null,则直接插入,若不为null,就要进行「哈希冲突处理」,依次去查找table[i]所挂载的结构数据,也就是遍历链表或红黑树,若key已存在就进行覆盖操作,否则就要找到合适的位置进行插入,链表就是找到尾巴元素,到了尾巴要判断链表和数组的长度看是否要进行扩容或转换结构,若符合条件则先进行扩容或转换结构之后再插入元素

  1. 当链表长度>=8且数组长度>=64时,会把链表转化为红黑树
  2. 当链表长度>=8,当数组长度<64时,会优先进行扩容,而不是转化为红黑树

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

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //判读table是否为空,为空则执行resize()扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //若hash值对应下标的table没有数据,则直接构造node插入
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //若key已存在,则覆盖原数据
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若是红黑树,则插入红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //循环遍历链表,查找要插入的位置
                for (int binCount = 0; ; ++binCount) {
                    //找到了尾巴结点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //若链表长度>=8,则进行扩容或转化为红黑树,并插入
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //插入
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize()扩容

数组长度扩展为原来的两倍,再遍历原的数组,把所有的节点重新hash到新数组中

  1. jdk1.7 :遍历所有的节点,依次通过hash函数计算新的下标,再插入到新数组的链表中。缺点:每个节点都需要进行一次求余计算;插入新数组采用的是头插法,在多线程环境下会形成链表环
  2. jdk1.8:控制数组的长度始终是2的整数次幂,每次扩展数组都是原来的2倍,key在新数组的hash结果只有两种:在原来的位置,或者在原来位置+原数组长度

get()

根据要查找的key计算hash值得到在table数组中的下标索引i,先check table[i]是否为目标值,若是就直接返回,不是就依次查找table[i]挂载的结构中的元素校验是否符合目标值

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

   
    final Node<K,V> getNode(Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
        //check table不为空,根据key计算得到hash值即为在table数据中的下标i
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & (hash = hash(key))]) != null) {
            //若table[i]的key和目标值相同,则找到了
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //遍历桶
            if ((e = first.next) != null) {
                //若是红黑树,则通过红黑树算法进行查找
                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);
            }
        }
        return null;
    }

常见问题

为什么jdk1.8需要加上红黑树的结构?

首先我们明确了无论是链表还是链表+红黑树都是为了解决哈希冲突的问题,那么从上文的get操作能看到,如果table[i]不是目标元素,就需要依次循环遍历table[i]挂载的结构,挨个对比结点数据是否为目标数据,链表的查询操作的时间复杂度是O(n),所以数据量越大冲突的几率就越大那么查询的效率就越慢,而红黑树的查找时间复杂度是O(logn),加上红黑树就是为了提高查询的速度

线程不安全(Fail-Fast机制)

举例:两个线程同时put,A线程在执行真正的插入操作时时间片到了,A线程挂起,然后B线程进来执行put操作,B线程结束后,A被调度,A的插入操作可能会把B刚刚插入的元素覆盖掉

  1. 如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这个结果是通过modCount字段实现的,顾名思义就是修改次数,每次对HashMap的修改都会增加这个值,而在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount
  2. 在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

污水处理环保设备厂商怎么选

在选择污水处理环保设备厂商时&#xff0c;需要综合考虑多个因素来确保选取的供应商能够提供高质量的设备和服务。以下是一些主要的考虑因素&#xff1a; 企业资质和认证&#xff1a;首先检查供应商是否拥有相关的资质证书和行业认证&#xff0c;例如ISO 9001质量管理体系认证、…

[Linux]一篇文章带你全面理解信号

文章目录 初识信号一、什么是信号二、为什么要有信号 看见信号一、先见一下Linux中的信号&#xff1a;二、如何产生信号三、自定义信号的处理行为&#xff08;自定义捕捉&#xff09; 了解信号一、信号的保存二、block、pending表使用代码查看三、一些倔强的&#xff0c;无法被…

亚马逊自养号测评策略:提升店铺产品权重的秘诀

对于卖家而言&#xff0c;拥有一款爆款产品无疑是获得流量的关键&#xff0c;同时它也能显著提升店铺的销量。因此&#xff0c;大部分卖家都热衷于学习如何打造爆款产品的策略&#xff0c;特别是对于那些致力于经营自己店铺的卖家来说&#xff0c;掌握这一技巧对于店铺的成功运…

JWT令牌技术实现登录校验

一.简单登录功能 在登录界面中&#xff0c;我们可以输入用户的用户名以及密码&#xff0c;然后点击 "登录" 按钮就要请求服务器&#xff0c;服务端判断用户输入的用户名或者密码是否正确。如果正确&#xff0c;则返回成功结果&#xff0c;跳转至系统首页面。 1.功能…

前端JS必用工具【js-tool-big-box】学习,生成uuid,数组去重

js-tool-big-box这个前端工具库&#xff0c;今天又添加了2个实用功能&#xff0c;分别是生成uuid和数组去重。 目录 1 安装并引入 2 生成uuid 3 数组去重 1 安装并引入 安装最新版的js-tool-big-box工具包 由于生成uuid和数组去重属于两个不同对象下的方法&#xff0c;所以…

照片尺寸怎么修改?这几个图片处理方式都可以

修改图片尺寸在许多场景中都是常见的需求&#xff0c;包括网页设计、图片编辑、手机应用程序开发、幻灯片演示、社交媒体和博客、以及打印和出版物设计&#xff0c;通过调整图片大小&#xff0c;可以适应不同的布局和设备要求&#xff0c;那么问题来了&#xff0c;如何将图片改…

国外客户怀疑我们产品质量要如何应对

经常有外贸小伙伴问我&#xff0c;国外客户怀疑我们的产品质量要如何应对&#xff1f; 这个问题应该算是外贸经常遇到的一个问题&#xff0c;今天就简单来给大家分享几个我认为可以去入手跟客户回复解决的这个问题的点。 首先&#xff0c;我们要知道&#xff0c;不管你做啥产品…

DBeaver配置离线驱动

因为部署的服务器为无网环境&#xff0c;所以在服务器上使用DBeaver需要配置离线驱动 我们在有网的环境下&#xff0c;安装DBeaver。把驱动下载下来&#xff0c;然后再拷贝到没网的设备上 一、下载驱动 1.在有网的设备上&#xff0c;打开DBeaver 2.找到窗口&#xff0c;选择…

【Linux】用户组、用户、文件权限(ugo权限),权限掩码,chmod,chown,suid,sgid,sticky,su,sudo

用户组 注意&#xff1a;普通用户只能查看有哪些组&#xff0c;不能创建/修改/删除&#xff0c;会提示&#xff1a;用户名 is not in the sudoers file.This incident will be reported. groupadd 用户组名新建用户组cat /etc/group查看有哪些组&#xff08;普通用户可以操作…

云原生 初识Kubernetes的理论基础

一、k8s 的由来及其技术运用 1.1 k8s的简介 Kubernetes&#xff0c;词根源于希腊语的 舵手、飞行员。在国内又称k8s&#xff08;因为k和s之间有8个字母&#xff0c;所以得名。“国内程序员的幽默”&#xff09;。 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#x…

【JAVA进阶篇教学】第十六篇:Java中AOP使用

博主打算从0-1讲解下java进阶篇教学&#xff0c;今天教学第十五篇&#xff1a;Java中AOP使用。 AOP&#xff08;Aspect-Oriented Programming&#xff09;是一种编程范式&#xff0c;它允许开发者在不修改源代码的情况下&#xff0c;对代码进行横切关注点的分离和增强。在 Java…

基于区块链的Web 3.0关键技术研讨会顺利召开

基于区块链的Web3.0关键技术研讨会 2024年4月23日&#xff0c;由国家区块链技术创新中心主办的“基于区块链的web3.0关键技术研讨会”召开。Web3.0被用来描述一个运行在“区块链”技术之上的“去中心化”的互联网&#xff0c;该网络上的主体掌握自己数据所有权和使用权&#xf…

python与anaconda 的对应关系

不能下载好anaconda 后才能知道python吧 python10。2023年3月 python11 2023年7月 具体请看官方说明 Anaconda 2023.09-0 — Anaconda documentation 示例如下&#xff0c;绿色框&#xff0c;有的在包的列表中搜python就可以找到

Qt自定义QpushButton分别在c++/python中实现

//.h文件#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QPainter> #include<QMouseEvent> #include<QPropertyAnimation> #include<QResizeEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; }class Widget : public QWi…

idea连接远程仓库

git ->克隆。 url为远程仓库的地址&#xff0c;输入好后&#xff0c;选择项目存放目录&#xff0c;再点击克隆 点击新窗口打开。 切换到对应分支

使用 Gin-Docs 自动生成 API 文档

该插件移植自 Python 的 Flask-Docs&#xff0c;可以根据代码注释生成文档页面&#xff0c;支持离线文档下载和生成&#xff0c;支持在线调试&#xff0c;支持密码认证。 Gin-Docs Gin API 文档自动生成插件 特性 根据代码注释自动生成 Markdown 文档支持离线 Markdown 文档下…

VBA 引用从SQL数据库取数据的几个方法

首先&#xff0c;要定义连接的数据集 Set objRec CreateObject("ADODB.Recordset")Set objConn CreateObject("ADODB.Connection")然后在代码中要定义SQL语句&#xff0c;以便获取数据 sqlstr sqlstr " select t1.FBillNo ,t_Item.fname type,t1…

【稀疏三维重建】pixelSplat:仅需两张图像的3D Gaussian Splats重建

文章目录 一.摘要二、相关工作 , 背景(gs)三、基于图像的三维高斯预测3.1 双视图图像编码器&#xff08;解决尺度模糊性&#xff09;3.2 &#xff08;像素对齐的&#xff09;高斯参数预测 四、实验效果 论文&#xff1a;《pixelSplat: 3D Gaussian Splats from Image Pairs for…

新串口通道打通纪实

在计算机系统中&#xff0c;串口是“古老”的通信方式&#xff0c;和它同时代的“并口”通信方式已经消失了。但它仍然顽强的存活着&#xff0c;主要原因是在开发和调试底层软件时还经常用到串口。 正因为有这样的需求&#xff0c;幽兰代码本是支持串口的&#xff0c;而且有两种…