HashMap源码分析——Java全栈知识(8)

news2024/11/25 1:04:42

jdk1.7和jdk1.8的HashMap的原理有一点出入我们就分开讲解:

1、JDK1.7中的HashMap

JDK1.7中的HashMap是通过数组加链表的方式存储数据。他的底层维护了一个Entry数组,通过哈希函数的计算出来哈希值,将待填数据根据计算出来的哈希值填入到对应位置。如果发生哈希冲突就以链表的方式存储在对应位置。形成链式结构。

2、JDK1.8中的HashMap

JDK1.8中的HashMap是数组+链表+红黑树的方式实现,底层维护了一个Node数组,当我们哈希冲突比较严重的时候,并且数组长度大于64(如果不足64优先扩容数组),链表长度大于等于8的时候,HashMap会将该链表转化为红黑树。值得注意的是如果此时我们减少红黑树中的节点,当节点数量小于等于6的时候,HashMap才会将红黑树转化位链表。

红黑树的好处

当哈希冲突比较严重的时候,也就是链表过长的时候,我们查找元素的时间复杂度就变成了O(n),也就是链表查找元素的时间复杂度,到那时我们哈希表就体现不出优势了。此时我们将链表转化位红黑树,而红黑树的查找时间复杂度是O(logN),就优化了HashMap的查找效率/

转化位红黑树的阈值为什么是6和8

因为如果我们频繁添加删除元素,使得链表的长度恰好是7左右,如果转化阈值是7则会频繁的转化位链表和红黑树,从而占用大量的CPU资源,设置为6和8就可以很好的避免此类情况。

哈希负载因子

默认的哈希负载因子是0.75,也就是如果当前元素/数组大小>=0.75,数组就必须要进行扩容。这个负载因子可以在构造函数中传入进行自定义。

3、HashMap的构造函数

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

HashMap有如上四个构造函数,参数分别是空,initialCapacity,initialCapacity和loadFactor,Map<? extends K, ? extends V> m

  • initialCapacity:指定HashMap的大小。
  • loadFactor:哈希负载因子。
  • Map<? extends K, ? extends V> m:Map集合。

值得注意的是如果我们不指定数组大小,那么HashMap的默认大小就是16。如果我们指定大小,例如33,那么会调用如下函数:

    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

我第一眼看到这个函数直接一句卧槽,这都什么呀。我们仔细算一下就知道,这个函数会返回比我们传入的值更大的最近的一个2进制值,例如传入33,返回的就是64.

也就是无论我们指定的数是几HashMap都会以2进制的大小进行初始化。这个跟HashMap的扩容机制有关,HashMap是以2倍的方式进行扩容

4、HashMap的put的过程

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

// 第四个参数 onlyIfAbsent 如果是 true,那么只有在不存在该 key 时才会进行 put 操作
// 第五个参数 evict 我们这里不关心
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 第一次 put 值的时候,会触发下面的 resize(),类似 java7 的第一次 put 也要初始化数组长度
    // 第一次 resize 和后续的扩容有些不一样,因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 找到具体的数组下标,如果此位置没有值,那么直接初始化一下 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) {
                // 插入到链表的最后面(Java7 是插入到链表的最前面)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个
                    // 会触发下面的 treeifyBin,也就是将链表转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果在该链表中找到了"相等"的 key(== 或 equals)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 node
                    break;
                p = e;
            }
        }
        // e!=null 说明存在旧值的key与要插入的key"相等"
        // 对于我们分析的put操作,下面这个 if 其实就是进行 "值覆盖",然后返回旧值
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
  1. 当我们调用Put方法的时候,会先检查数组是否为空,如果为空则初始化数组。
  2. 判断key是否为null,如果是null则会把对应的value值放到数组下标为0位置。
  3. 根据key算出来hash值,找到对应位置查找,如果有该key,则覆盖原来的value值
  4. 如果没有该key(新值),则把key和value以Node的形式存放到当前位置,jdk1.7采用头插法,jdk1.8采用尾插法。
  5. 判断是否需要转化为红黑树,也就是链表长度是否到达8.
  6. 判断是否扩容。

值得注意的是JDK1.7是先扩容再插入,1.8是先插入再扩容。

5、HashMap的扩容原理

HashMap是默认以两倍的大小进行扩容。

例如我们的HashMap的数组从16扩容到32,如图所示

链表中的节点值会有序的分布在新链表的 i 位置和 i+16 的位置。省去了重新计算hash值的过程,而且分布的更加均匀。

6、HashMap线程安全吗?

HashMap是一个线程不安全的集合,如果两个线程同时操作Map扩容,就会产生循环链表的现象。

如何得到一个线程安全的Map

  1. 使用Collections工具类,将线程不安全的Map包装成线程安全的Map;
  2. 使用java.util.concurrent包下的Map,如ConcurrentHashMap;
  3. 不建议使用Hashtable,虽然Hashtable是线程安全的,但是性能较差。

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

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

相关文章

memset的用处

这个memset是真的究极坑中坑,这玩意对int数组是压根没法初始化-1,0其他任何数,以后除了-1和0,其他的一概不能用这玩意,这个是真的坑,一旦出了错巨难找 初始-1 初始0 初始1

JTS: 13 Polygonizer 多线合成面

这里写目录标题 版本代码 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 线段 生成之后的面 public class GeometryPolygonization {private static final GeometryFactory geometryFactory new GeometryFactory();private static final Logger LOGGER …

Vue2别踩白块(第二种)

效果图: 点击黑块变灰 游戏结束 功能简介 点击白块直接失败,点击黑块得计一分。 代码逻辑 其实和第一种类似,唯一区别在于此种方式的判断滚动到底部是否有违背点击的黑块的算法。 1、数组存放白块数据:二维数组,数组内部单个元素为一个四位数字的数组,其中1代表黑块,0代…

移动硬盘只读模式怎么取消?

当硬盘驱动器处于为只读模式时&#xff0c;您仅能读取保存在该驱动器中的数据&#xff0c;但却无法添加新数据和修改当前数据。如果您想要对数据做一些改变就需要取消只读模式。那么&#xff0c;移动硬盘只读模式怎么取消&#xff1f; 方案一&#xff1a;使用命令提示符移除只读…

生产环境docke问题排查

查看进程top查看具体的线程 top -H -p 8898如果cpu 过高&#xff0c;就是有问题的地方&#xff1b; 接下来根据docker查看具体的问题 查看dockers容器哪个内存、cpu占用过高 docker stats前言&#xff1a; 有java 启动容器&#xff1b;有jre包启动的容器。如下图 根据cpu很高…

Microsoft 365一键安装、激活!(附安装包)

下面将介绍的是Microsoft 365一键安装。 安装包&#xff1a;https://dnmjun.lanzout.com/iBFGZ1ddxql 安装步骤 1、下载得到安装包后&#xff0c;先解压&#xff01; 2、双击OfficeSetup开始安装 3、进入正式安装了&#xff0c;耐心等即可。 4、下载过程会需要点时间&#x…

Data Uncertainty Learning in Face Recognition

传统的面部识别方法即使在图片中面部模糊的情况下&#xff0c;耶给出确定的面部识别特征 事实上&#xff0c;这种模糊的代表着数据的不确定性&#xff0c;这个网络向我们展示了在不确定视角下&#xff0c;简单的回归任务和面部识别回归任务共享同样的模式 在这篇论文中&#xf…

数控机床数字孪生可视化平台,推动智能装备水平整体提升

作为智能制造的中重要装备之一&#xff0c;数控机床正朝着智能化、精密化、数字化、可视化的目标发展。数字孪生技术是一种新兴的技术&#xff0c;可以将物理机床仿真为虚拟机床&#xff0c;以实现更高效的数控机床调试。数字孪生是指将实际物理系统与其数字化的虚拟模型相结合…

Confluence未授权管理用户添加漏洞复现 (CVE-2023-22515)

Confluence未授权管理用户添加漏洞复现 【CVE-2023-22515】 一、漏洞描述二、漏洞影响版本三、网络空间测绘查询四、漏洞复现1.手动复现2.自动化复现小龙POC检测nuclei检测 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信…

python脚本-requests模块

python脚本-requests模块 模拟浏览器 import requests url"http://10.9.47.154/php/arrayprac/get.php" headers {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Sa…

创建ABAP数据库表和ABAP字典对象-使用已存在的数据元素增加城市字段04

基于内置域增加一个字段 1.在编辑器中&#xff0c;输入字段的名称&#xff0c;后跟冒号:city:。暂时忽略这个错误。2. 输入/MOC/C并使用自动补全(**Ctrl空格**)&#xff0c;输入类型。3. 然后添加一个分号:city: /moc/city;4.在SAP GUI中查看&#xff0c;字段已经新增

Java-数组的定义与使用

本章重点&#xff1a; 1. 理解数组基本概念 2. 掌握数组的基本用法 3. 数组与方法互操作 4. 熟练掌握数组相关的常见问题和代码 1. 数组的基本概念 1.1 为什么要使用数组 public class TestStudent{public static void main(String[] args){int score1 70;int s…

子管理员运营权限管理,实现更加精细的权限控制,管理各个小组的学员、题库和考试

土著刷题Plus专业版&#xff0c;以【录题-分组-刷题-考试】为中心打造一套完备的在线组卷刷题学习平台&#xff0c;自定义品牌名称和Logo&#xff0c;入驻后&#xff0c;您将拥有自己独立的企业级专业运营管理平台。 土著刷题Plus专业版v1.3版本&#xff0c;我们迭代了运营权限…

java--类和对象的一些注意事项

1.类和对象的一些注意事项 ①类名建议用英文单词&#xff0c;首字母大写&#xff0c;满足驼峰模式&#xff0c;且要有意义&#xff0c;比如&#xff1a;Student、Car...(只是建议&#xff0c;你其什么名字都没关系&#xff0c;只是当大部分人都在用这条规矩的时候&#xff0c;…

同时标注分割、检测、多分类属性的工具

1、 https://blog.csdn.net/minstyrain/article/details/82385580/ 2、 https://zhuanlan.zhihu.com/p/656703406

动态规划算法实现------转换(编辑、变换)问题

目录 一、字符串转换问题 1.1问题 1.2确定动态规则(DP、状态转移方程)、初始值 (1)插入操作实现状态转移 (2)删除操作实现状态转移 (3)替换操作实现状态转移 (4)初始值 1.3动态规划算法代码实现 (1)完整代码 (2)程序速度优化 二、矩阵变换问题 2.1问题 2.2矩阵乘法 (1)矩阵相乘…

苹果M3 芯片首个Geekbench跑分出炉 单核成绩比M2 Ultra高9%

在10.31的“来势迅猛”发布会上&#xff0c;苹果正式发布了 M3、M3 Pro、M3 Max 芯片。同时&#xff0c;苹果还发布了搭载 M3 芯片的新一代 24 英寸 iMac&#xff0c;10999 元起。 这是首款采用 3 纳米工艺技术的 PC 芯片&#xff0c;实现了 Apple 芯片史上最大幅的图形处理器架…

SpringBoot 继承 Apollo 应用 简单步骤记录

1、pom.xml 添加依赖&#xff1b; <!--本地项目涉及到该sdk版本冲突时可能需要手动解决版本控制问题--><dependency><groupId>com.ctrip.framework.apollo</groupId><artifactId>apollo-client-tair</artifactId><version>1.4.0<…

c++中httplib使用

httplib文件链接:百度网盘 请输入提取码 提取码:kgnq json解析库:百度网盘 请输入提取码 提取码:oug0 一、获取token 打开postman, 在body这个参数中点击raw,输入用户名和密码 然后需要获取到域名和地址。 c++代码如下: #include "httplib.h" #in…

330kv变电站运维检修vr系统大幅提高管理工作效率和安全水平

安全是电力生产的基石&#xff0c;确保电网安全和人身安全&#xff0c;是电网企业安全工作的出发点和落脚&#xff0c;针对大批量的无人值守变电站训练&#xff0c;开展严格、规范、安全、高效的远程巡检培训至关重要&#xff0c;无人值守变电站VR远程智慧巡检通过VR虚拟现实制…