一篇文章带你走进Java集合中的HashMap

news2024/10/19 1:27:21

文章目录

  • 一、了解常见的数据结构
    • 二叉平衡树
    • AVL树
    • 红黑树
  • 二、HashMap的实现原理
    • HashMap的jdk1.7和jdk1.8有什么区别?
  • 三、HashMap put的具体流程
  • 四、HashMap的扩容机制
  • 五、HashMap的寻址算法
    • **第一步:** 计算对象的hashCode
    • 第二步: 二次哈希
    • 第三步:计算索引
    • 为什么HashMap的数组长度一定是2的次幂
  • HashMap在1.7情况下的死循环问题
  • ConrurrentHashMap
    • jdk1.7中
    • jdk1.8中

在这里插入图片描述

一、了解常见的数据结构

二叉平衡树

  • 若左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若右子树不为空,则右子树上所有节点的值都大于跟节点的值
  • 它的左右子树也都是二叉平衡树

在这里插入图片描述
查找效率

  • 最优情况,完全二叉树,log2N
  • 最差情况,单只树,N/2在这里插入图片描述

AVL树

  • 左右子树都是AVL树
  • 左右子树高度之差(平衡因子)的绝对值不超过1
    在这里插入图片描述
    查找效率
    log2N

红黑树

红黑树是一种二叉搜索树,在每个结点增加一个存储位,表示颜色,可以是Red或Black。通过任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

在这里插入图片描述
红黑树的性质

  1. 最长路径最多是最短路径的两倍
  2. 每个节点不是红色就是黑色
  3. 根节点是黑色
  4. 如果一个节点是红色,则它的两个孩子节点是黑色
  5. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含数量相同的黑色节点
  6. 每个叶子节点都是黑色的

二、HashMap的实现原理

HashMap的数据结构:底层使用hash表数据结构,即数组+链表或红黑树

  1. 当向HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况
    a. 如果key相同,则覆盖原值
    b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中
  3. 获取时,直接找到hash值对应的下标,再进一步判断key是否相同

HashMap的jdk1.7和jdk1.8有什么区别?

  • JDK1.8之前采用的是:数组+链表。如果遇到哈希冲突,则将冲突的值添加到链表中
  • JDK1.8中,当链表长度大于阈值(默认为8)并且数组的长度达到64时,则将链表转位红黑树,以减少搜索时间,当扩容时,红黑树拆分成树的结点小于等于临界值6个时,会退化成链表

三、HashMap put的具体流程

HashMap是懒惰加载,在创建对象时并没有初始化数组
在无参的构造函数中,设置了默认的负载因子为0.75
源码

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

    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            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);
                        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;
    }

流程图
在这里插入图片描述

  1. 判断键值对数组table是否为空或者为null,否则执行resize()进行扩容(初始化)
  2. 根据键值key计算hash值得到数组索引
  3. 判断table[i]==null,条件按成立,则直接新建节点天机
  4. 如果table[i]==null,不成立
    4.1 哦按段table[i]的首个元素是否和key一样,如果相同则直接覆盖value
    4.2 判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是,则直接在树中插入键值对
    4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话将链表转换成红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在则直接覆盖value
  5. 插入成功后,判断实际存在的键值对数量size是否多余最大容量threshold(数组长度*0.75),如果超过,则扩容

四、HashMap的扩容机制

扩容流程
在这里插入图片描述

  • 添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到扩容阈值(数组长度*0.75)
  • 每次扩容时,都是扩容之前容量的2倍
  • 扩容之后,会创建一个数组,需要把老数组中的数据挪动到新的数组中
    – 没有hash冲突的节点,直接使用e.hash & (newCap -1) 计算数组的索引位置
    – 如果是红黑树,走红黑树的添加
    – 如果是链表,则需要遍历链表,可能需要拆分链表,判断e.hash & oldCap 是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

五、HashMap的寻址算法

第一步: 计算对象的hashCode

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

第二步: 二次哈希

二次哈希
先将hash值右移16位,再与原来的hash值异或运算

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

为什么这么麻烦,不直接使用hash值呢?
这样做是为了让hash值更加均匀,减少哈希冲突

第三步:计算索引

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
     ------
    if ((tab = table) == null || (n = tab.length) == 0)
  	 ------
}

(n-1)&hash:得到数组的索引,代替取模,性能更好。数组长度必须是2的次幂

为什么HashMap的数组长度一定是2的次幂

  1. 计算索引时效率更高,如果是2的n次幂,可以使用位运算代替取模
  2. 扩容时重新计算索引效率更高:hash & oldCap 的元素留在原来位置,否则新位置 = 旧位置 + oldCap

HashMap在1.7情况下的死循环问题

在jdk1.7的hashmap中在数组进行扩容时,链表采用的是头插法,在数据迁移时,可能导致死循环

现在有两个线程
线程一: 读取到当前的hashmap数据,数据中一个链表在准备扩容时,线程二介入
线程二:也在读取hashmao,直接进行扩容,因为是头插法,链表的顺序会进行颠倒,比如原来是AB,扩容后是BA,线程二执行结束
线程一:继续执行时就会出现死循环
线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A,所以B-》A-》B形成了循环。

JDK8将扩容算法进行了调整,使用尾插法,就避免了jdk7中的死循环问题

ConrurrentHashMap

ConrurrentHashMap是一种线程安全的Map集合

jdk1.7中

底层结构: 分段数组+链表
在这里插入图片描述

在计算得到在Segment数组中的下标位置后,,会进行加锁(ReentrantLock锁),然后再通过hash值定位在HashEntry数组中的位置, 性能比较低
在这里插入图片描述

jdk1.8中

底层结构: 数组+链表/红黑树
底层用到了CAS和Sunchronized来保证并发安全

  • CAS控制数组节点的添加
  • synchronzied只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,快就不会产生并发的问题,效率得到提升。锁的粒度更细

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

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

相关文章

基于springboot的学习平台系统

springbootjava学习平台作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a…

2024第七届全国大学生数学竞赛暨数学精英挑战赛第二场,第一场获奖名单已公布

第七届全国大学生数学竞赛暨数学精英挑战赛&#xff08;原网络挑战赛&#xff09;由浙江应用数学研究会组织举办。自2018年起已经连续举办6届&#xff0c;吸引来自全国900余所高校选手参赛&#xff0c;近三年累计参赛人数1.5万人。2024年&#xff0c;第七届全国大学生数学竞赛暨…

力扣MySQL 1581

先把两张表连接&#xff0c;amount为null 的正是我们需要的&#xff0c;再按customer_id聚合 select Visits.visit_id,customer_id ,Transactions.visit_id ,transaction_id ,amount from Visits left join Transactions on Visits.visit_idTransactions.visit_id 正确代码&…

银河麒麟V10系统+Windows10双系统启动顺序正确修改方法

***正确可行方法***&#xff0c;测试OK且稳定&#xff1b; 银河麒麟桌面操作系统V10是一款适配国产软硬件平台并深入优化和创新的新一代图形化桌面操作系统&#xff0c;同源支持国内外主流处理器架构&#xff0c;并不断使能GPU、桥片、网卡等各种新硬件&#xff0c;提供更优的软…

Electron+Vue实现两种方式的截屏功能

本次介绍的截屏功能一共有两种分别是在electron环境中与非electron环境中 非electron环境 这个环境下会有一些限制&#xff1a; 1.只能截浏览器中的画面 2.如果里面有iframe或者base64的图片会加载不出来&#xff08;这个会有解决办法&#xff09; yarn add -D js-web-scree…

Vue详细入门(语法【三】)

今天滴的学习目标&#xff01;&#xff01;&#xff01; Vue组件是什么&#xff1f;组件的特性和优势Vue3计算属性Vue3监听属性 在前面Vue详细入门&#xff08;语法【一】——【二】&#xff09;当中我们学习了Vue有哪些指令&#xff0c;它的核心语法有哪些&#xff1f;今天我们…

大模型学习方法之——大模型技术学习路线,小白也能学会大模型

技术学习无非涵盖三个方面&#xff0c;理论&#xff0c;实践和应用 大模型技术爆火至今已经有两年的时间了&#xff0c;而且大模型技术的发展潜力也不言而喻。因此&#xff0c;很多人打算学习大模型&#xff0c;但又不知道该怎么入手&#xff0c;因此今天就来了解一下大模型的…

java-uniapp小程序-引导关注公众号、判断用户是否关注公众号

目录 1、前期准备 公众号和小程序相互关联 准备公众号文章 注册公众号测试号 微信静默授权的独立html 文件 2&#xff1a; 小程序代码 webview页面代码 小程序首页代码 3&#xff1a;后端代码 1&#xff1a;增加公众号配置项 2&#xff1a;读取公众号配置项 3&…

【学习笔记】什么是MongoDB

文章目录 MongoDB 简介体系结构数据模型MongoDB 的特点 MongoDB 简介 学习一个东西就跟认识一个人一样&#xff0c;下面有情MongoDB来做个自我介绍 大家好&#xff0c;俺是MongoDB&#xff0c;是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计俺就是用于简化开…

学习笔记(202410)

课程&#xff1a;Generative AI for Software Development 链接&#xff1a;吴恩达同步最新AI专业课&#xff0c;第54讲&#xff1a;用人工智能做软件开发--Generative AI for Software Development_哔哩哔哩_bilibili 时间&#xff1a;2024-10-12 至 概述&#xff1a;使用C…

强化学习与深度强化学习:深入解析与代码实现

个人主页&#xff1a;chian-ocean 文章专栏 强化学习与深度强化学习&#xff1a;深入解析与代码实现 强化学习&#xff08;Reinforcement Learning, RL&#xff09;是一种机器学习方法&#xff0c;通过智能体&#xff08;agent&#xff09;与环境&#xff08;environment&am…

【Linux】Linux常见指令及权限理解

1.ls指令 语法 &#xff1a; ls [ 选项 ][ 目录或文件 ] 功能 &#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项&#xff1a; -a 列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文…

无人机视角下火灾检测数据集 共12736张 标注文件为YOLO适用的txt格式。已划分为训练集、验证集、测试集。类别:Fire yolov5-v10通用

无人机视角下火灾检测数据集 共12736张 标注文件为YOLO适用的txt格式。已划分为训练集、验证集、测试集。类别&#xff1a;Fire yolov5-v10通用 无人机视角下火灾检测数据集 共12736张 标注文件为YOLO适用的txt格式。已划分为训练集、验证集、测试集。类别&#xff1a;Fire yol…

【Vue】Vue3.0(十)toRefs()和toRef()的区别及使用示例

上篇文章&#xff1a;Vue】Vue&#xff08;九&#xff09;OptionsAPI与CompositionAPI的区别 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月15日11点13分 文章目录 toRefs()和toRe…

基于朴素贝叶斯的中文垃圾短信分类(含ui界面)

完整代码如下 基于朴素贝叶斯的中文垃圾短信分类、垃圾邮件分类。 可用于机器学习课程设计等。 import warnings warnings.filterwarnings(ignore) import os os.environ["HDF5_USE_FILE_LOCKING"] "FALSE" import pandas as pd import numpy as np fro…

LeetCode1004.最大连续1的个数

题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09; 1.常规解法&#xff08;会超时&#xff09; 遍历数组&#xff0c;当元素是1时个数加一&#xff0c;当元素是0时且已有的0的个数不超过题目限制时&#xff0c;个数加一&#xff0c;若上…

重生之我爱上了k8s!

内容不全&#xff0c;待补充中...... 目录 一、k8s的部署 1.1.集群环境初始化 1.1.1.所有主机禁用swap 1.1.2.安装k8s部署工具 1.1.2.所有节点安装cri-docker 1.1.3.在master节点拉取K8S所需镜像 1.1.4.集群初始化 1.1.5.其他两台主机加入集群 1.1.6.安装flannel网络…

微积分复习笔记 Calculus Volume 1 - 2.5 Precise Definition of a Limit

2.5 The Precise Definition of a Limit - Calculus Volume 1 | OpenStax

Python基础——类与对象

类与对象的理解&#xff1a; 在程序中我们将类看作是设计图纸&#xff0c;对象则是根据这个图纸生产的产品。面向对象编程就是使用对象编程&#xff0c;在类中我们定义成员属性和方法。 来看下面这个例子&#xff0c;创建student类&#xff0c;定义对象并对属性赋值。 class S…

Golang | Leetcode Golang题解之第480题滑动窗口中位数

题目&#xff1a; 题解&#xff1a; type hp struct {sort.IntSlicesize int } func (h *hp) Push(v interface{}) { h.IntSlice append(h.IntSlice, v.(int)) } func (h *hp) Pop() interface{} { a : h.IntSlice; v : a[len(a)-1]; h.IntSlice a[:len(a)-1]; return v }…