首页 > 其他分享 >HashMap 详细解析

HashMap 详细解析

时间:2024-07-31 13:25:21浏览次数:7  
标签:Node 详细 null HashMap int key 解析 oldCap

HashMap

HashMap 是 Java 的一种键值对容器,该容器的核心方法是 putVal 方法,为了提高查询稳定性,Java 团队向该类引入了红黑树结构,为了减少碰撞概率引入了扰动函数,在对象的哈希值相同时又调用了 equals 方法进行二次检测

泊松分布

* 树形节点的大小是普通节点的两倍
* 桶的数量和节点的数量的关系呈泊松分布
* 树化阈值 8 解树化阈值 6 初始容量为 16 是经验的选择
* 默认情况下,当 HashMap 中的元素个数超过其容量的 3/4 便会触发扩容
* 初始容量为 16 第一次扩容后 32 第二次扩容后 64, λ 分别为 12 24 48

* 横坐标代表初始容量,纵坐标代表发生冲突的概率,面积代表桶中元素的个数(与 capacity 无直接联系)。
* λ 与 冲突概率 P(K) 和柱状图的面积呈正比
* λ 的初始值为 0.75 初始 capacity 为 16 是容量大冲突概率小的均衡选择
* λ = loadFactor * capacity 当 HashMap 超过此数时,就会触发 HashMap 的扩容操作

* 红黑树最好 O(logn) 最坏 O(logn)
* 链表 + 数组最好 O(1) 最坏 O(N)
* 为了保证系统查询的稳定性 Java 在 JDK 1.8 中使用了 数组 + 链表 <-----> 红黑树的结构

允许为空

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

* 为什么只允许一个 key 为 null 的对象?
    当 key 为 null 时 HashMap#hash 只会返回 0

* 为什么可以允许多个 key !=null 但 value == null 的键值对?
    HashMap#putVal 不会对 value == null 做出检查,只要 key 不同就会允许插入  

扰动函数

// 哈希冲突无法解决只能尽力避免
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
* Java 在计算对象在 HashMap 的位置时利用了最优化问题中的扰动函数,基本思路是:先将对象的 hasCode 右移 16 位,获得大概四十亿的映射空间中的一个值,
再将该值和原先的 hashCode 做异或运算进行缩放,使之能够放入 HashMap 中

* 当一个对象的 hasCode 足够特殊时(很小或是个奇数)经扰动函数扰动后结果仍可能相同,此时通过 equals 比较是否相等是避免碰撞的最好方法

以二为底的幂

* 同一时期 HashMap 的长度是固定的
* 与运算: 0 与 1 为 0,1 与 1 为 1
* e = tab[index = (n - 1) & hash]
* 以 2 为底的 k 次幂的值减一后的结果的二进制表示是 k 个一
* 以 2 为底的幂可以保证 HashMap 中的每一个位置发生冲突的概率是相同的
* HashMap 未对扩容和插入删除做同步设计,并发环境下请使用 HashTable 或 ConcurrentHashMap
static final int tableSizeFor(int cap) {
        // 无符号左移高位补零
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        // 溢出返回一
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
// 计算一个 int 类型的整数补码的前导零的数量
public static int numberOfLeadingZeros(int i) {
        // HD, Count leading 0's
        if (i <= 0)
            return i == 0 ? 32 : 0;  // 0 的补码全是零,负数没有前导零
        int n = 31;
        /*
        32 位整数,每 8 位一段,从 1 开始,最多有 31 个 0
        */
        // i >= 2 ** 16
        if (i >= 1 << 16) { n -= 16; i >>>= 16; }  // 65535
        // i >= 2 ** 8
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; }  // 256
        // i >= 2 ** 4
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; }  // 32
        // i >= 2 ** 2
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; }  // 2
        return n - (i >>> 1);
    }
final Node<K,V>[] resize() {  // resize 用于调整 HashMap 的大小,不只用于扩容
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            // oldCap 超出最大容量时不再扩容
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            } // 两倍扩容 
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 下一次扩容阈值为上一次的两倍
                newThr = oldThr << 1; // double threshold
        } // 使用单参构造方法,在第一次会缩小至 0.75 * capacity
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {              // zero initial threshold signifies using defaults
            // 使用空构造方法,在第一次直接扩容至 16,阈值扩大为 12
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {  // 进行超出写死,确保不会扩容或确定单参构造函数的下次扩容阈值
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;// 进行复制
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {  // 数组和链表
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)  // 红黑树
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

标签:Node,详细,null,HashMap,int,key,解析,oldCap
From: https://blog.csdn.net/weixin_63594548/article/details/140813709

相关文章

  • Vue3 - 最新详细实现网站内部打开预览 office 全套附件,在页面弹窗内解析预览 word文档
    前言如果您需要Vue2版本,请访问这篇文章。在vue3|nuxt3项目开发中,详解实现项目内部“打开解析预览各种office文档”通用预览插件,支持弹出一个窗口在弹框内预览或者直接显示在页面某个div容器里面,解析预览word文档、excel电子表格、ppt演示文稿、pdf文档、txt文......
  • Vue2 - 最新详细实现网站内部打开预览 office 全套附件,在页面弹窗内解析预览 word文档
    前言如果您需要Vue3版本,请访问在vue2|nuxt2项目开发中,详解实现项目内部“打开解析预览各种office文档”通用预览插件,解析预览word文档、excel电子表格、ppt演示文稿、pdf文档、txt文本等,支持弹出一个窗口在弹框内预览或者直接显示在页面某个div容器里面,让vue项......
  • Spring循环依赖+案例解析
    什么是Spring中的循环依赖?循环依赖是指两个或者多个bean互相依赖对方,从而形成一个闭环。例如:BeanA依赖于BeanB,而BeanB又依赖于BeanA。可能会导致Spring在尝试创建这些bean实例时出现问题,因为他们互相等待对方被创建,最终导致应用程序无法启动。Spring是如何发现这种循环依赖......
  • java注解与反射(非常详细, 带有很多样例)
    下面是详细地讲解Java中的注解与反射,并提供了很多的示例来帮助理解。Java注解(Annotations)1.注解的基本概念注解(Annotation)是Java5引入的一种用于为代码元素(类、方法、字段、参数等)添加元数据的机制。这些元数据可以在编译时、类加载时或运行时被读取并使用。注解不会直......
  • 奶奶都能学会的Linux系统nginx安装详细过程
    nginx安装安装前准备下载nginx源码包编译安装服务优化控制命令编辑网站首页访问验证Nginx的优点包括:性能高效,适合高并发环境资源消耗低,适合资源受限的环境配置简单,易于理解和修改轻量级,占用较少内存可靠性高,长时间运行中保持稳定性安装前准备1.依赖下载[root......
  • Android开发 - (适配器)Adapter类中ArrayAdapter实现类详细解析
    作用将数组数据映射到UI组件(如ListView、Spinner等)上的角色。它是BaseAdapter的一个子类,专门用于处理简单的数据集合,如数组或列表。ArrayAdapter简化了数据到视图映射的过程,使得开发者能够以更少的代码实现数据的展示。它的主要作用为以下几点:数据绑定:它能够将一组数据......
  • Systemd 解释使用实例(Linux系统的详细解释和配置文件使用)
    Systemd是Linux系统工具,用来启动守护进程 (opensnewwindow),已成为大多数发行版的标准配置。1.由来历史上,linux的启动一直采用init进程。下面的命令用来启动服务。$sudo/etc/init.d/apache2start#或者$serviceapache2start这种方法有两个缺点。一是启动......
  • SpringIoC-依赖注入源码解析
    目录1.依赖注入底层原理流程图2.Spring中到底有几种依赖注入的方式?2.1手动注入 2.2自动注入2.2.1XML的autowire自动注入2.2.2@Autowired注解的自动注入3.寻找注入点3.1static的字段或方法为什么不支持3.2桥接方法4.注入点进行注入4.1字段注入4.2Set......
  • 【运筹学】怎样更好地理解和应用单纯形法(温习、深刻反思、详细整理)
    1对单纯形法的理解    假设线性规划问题存在可行域;1.1预备知识点    (1)线性规划问题的标准化【运筹学】线性规划问题的标准化及其意义(针对单纯形法)        (2)线性规划问题的可行域是凸集;    (3)如果一个线性规划问题存在唯一最优解,那么最优......
  • Midjourney应用-动漫形象的系列插图绘制(详细操作与应用思路)
    Midjourney中如何去生成动漫形象的系列插图?在保持角色形象不变的情况下,生成动作、表情、神态都不同的系列插图一次性生成偏向教学图像的动漫形象生成这次我们讲的动漫形象生成除了是系列图还会更偏向教学图像就好像学动画设计专业的教科书上,会有特别细致的角色创......