首页 > 编程语言 >【JavaSE】数据结构-哈希表(HashSet/HashMap底层哈希表详解,源码分析)

【JavaSE】数据结构-哈希表(HashSet/HashMap底层哈希表详解,源码分析)

时间:2023-12-09 17:14:03浏览次数:36  
标签:hash HashMap tab 哈希 数组 源码 key null

哈希表结构

JDK8版本之前:数组+链表


JDK8版本及之后:数组+链表+红黑树

哈希表HashMap put()方法的添加流程

  1. 创建HashSet集合时,构造方法中自动创建HashMap集合;
    HashMap空参构造方法会创建一个默认长度为16,默认加载因子为0.75的数组,数组名为table
    (tips:实际上,HashSet对象创建后,第一次调用add方法时table数组长度才会变为16,初始情况下table数组为空)

  2. 调用集合的添加方法,会计算元素的索引位置——哈希值 % 数组长度

  • 首先,计算哈希值
    为了尽可能避免哈希冲突,使集合元素的索引位置尽可能分散,以此减少链表的挂载数量,java底层哈希值运算分为三个步骤:

    (1) 原始哈希值:是Object类的hasCode()调用底层C++代码计算出的一个随机数(也就是地址值)
    (2) 扰动哈希:整数二进制的后16位变化不大,取模容易产生哈希冲突,因此(无符号)右移原始哈希值,高16位补0,使原高16位的变化较大的数据参与运算
    (3) 二次哈希:然后使原始哈希值与扰动哈希值进行异或进行二次哈希

  • 然后,取模数组长度

    这里使用[数组长度-1] & 哈希值来替代哈希值 % 数组长度,因为位运算&比取模%计算速度快

  1. 判断索引位置元素是否是null
    是->存入;
    不是->说明有元素,调用equals方法比较内容,如果元素内容不同,则使用尾插法插入到该位置元素的尾部

  2. 当某索引位置挂载的元素过多,查询性能会降低,如何提高查询性能?

  • 扩容数组
    (1)当数组中的元素个数到达了(>=)16*0.75(加载因子)=12时,扩容原数组2倍的大小
    (2)链表挂载的元素超过了(>)8个(阈值),并且数组长度没有超过(<)64,扩容原数组2倍的大小
  • 链表转红黑树
    链表挂载的元素超过了(>)8个(阈值),并且数组长度达到了(>=)64,使链表转红黑树

源代码:

HashSet.java
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
HashMap.java
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

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

    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;
    }


    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
Object.java
    @IntrinsicCandidate
    public native int hashCode(); // 调用底层C++代码计算出的一个随机数(也就是地址值)

HashSet和HashMap哈希表结构的存储区别

HashSet哈希表结构:就是上述哈希表结构
HashMap哈希表结构:将键值对封装为Entry对象,然后取键做哈希值运算计算索引位置并存储

标签:hash,HashMap,tab,哈希,数组,源码,key,null
From: https://www.cnblogs.com/Eve7Xu/p/17888981.html

相关文章

  • 直播系统源码,常见的混音算法有哪些?
    声音是由于物体的振动对周围的空气产生压力而传播的一种压力波,转成电信号后经过抽样,量化,仍然是连续平滑的波形信号,量化后的波形信号的频率与声音的频率对应,振幅与声音的音量对应,在直播系统源码中,量化的语音信号的叠加等价于空气中声波的叠加,所以当采样率一致时,混音可以实现为将各......
  • 成品直播源码,如何在开发时自定义缓存策略
    缓存在成品直播源码中所占用的空间往往会成为迫使用户卸载应用的最后一根稻草。开发者不能无上限对音视频资源进行缓存,通常的维护手法是通过限制空间大小,比如,用户通常可以接受视频类应用有1G左右的缓存空间,即时通信类应用也许会更大些。因此我们的成品直播源码缓存库也需要提供......
  • 视频直播app源码,在开发时配置 lint 风格检查与修正
    在开发视频直播app源码时引入工具辅助,可以强制性地实现编码书写和提交过程中的lint校验。下面以当前流行的GitHook方案举例供参考。一、开发编辑器及lint工具配置我们在视频直播app源码中配置TSLint插件以校验typeScript;配置styleLint插件以校验CSS/LESS。我们约定......
  • 字符串哈希
    单哈希且用自然溢出代替取模操作,常数小但是容易被卡单字符串区间内比较typedefunsignedlonglongULL;constintN=100010,P=131;intn,m;charstr[N];ULLh[N],p[N];ULLget(intl,intr){returnh[r]-h[l-1]*p[r-l+1];}intmain(){......
  • 基于mysql、laravel、vue2框架开发的手术麻醉临床信息系统源码,自主版权
    手术麻醉系统源码技术架构:PHP、js、mysql、laravel、vue2手术麻醉临床信息管理系统是数字化手段应用于手术过程中的重要组成部分,用数字形式获取并存储手术相关信息,既便捷又高效。既然是管理系统,那就是一整套流程,管理患者手术、麻醉的申请、审批、安排以及术后有关各项数据的记录......
  • Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)
    一、前言在本系列文章:SpringSecurity6.x系列(4)——基于过滤器链的源码分析(一)中着重分析了SpringSecurity在SpringBoot自动配置、 DefaultSecurityFilterChain和FilterChainProxy 的构造过程。SpringSecurity6.x系列(7)——SecurityBuilder继承链源码分析中详细分析了......
  • PACS医学影像报告管理系统源码带CT三维后处理技术
    从各种医学影像检查设备中获取、存储、处理影像数据,传输到体检信息系统中,生成图文并茂的体检报告,满足体检中心高水准、高效率影像处理的需要。自主知识产权:拥有完整知识产权,能够同其他模块无缝对接国际标准:按照国际规范DICON3.0标准处理医学影像数据无缝对接:无缝对接各种体检影......
  • Nacos源码(七):客户端实例变更事件机制源码分析
    在给出的NamingExample示例中,给出客户端订阅的代码,详情如下:客户端的订阅机制是通过事件完成的,NacosNamingService#subscribe()详情如下:客户端订阅主要步骤:1、注册事件监听器2、客户端订阅客户端订阅在Nacos源码(六):客户端服务发现源码分析中已经做了......
  • C#区域医院检验系统源码,SaaS服务
    LIS是将医院检验仪器与计算机组成网络,使得医院检验工作从医生检验申请——收费——样品采样与登记——数据采集与审核——报告单传输与打印——质量控制——统计与分析等一系列流程中,实现智能化、自动化和规范化,大大提高了业务效率。本套LIS检验系统面向医院实验室、医院集团、区域......
  • Nacos源码(五):服务端健康检查源码分析
    服务注册到Nacos后,其他服务就可以获取该服务的实例信息,调用此服务;当服务宕机,Nacos会将该服务信息从维护的服务实例列表中删除,此时,其他服务获取不到该服务的实例信息,无法调用该服务。该服务是否应该被删除,取决于该服务是否健康,Nacos提供健康检查机制,判断服务是否有问题,将不健康......