HashMap 的实现原理和扩容原理是怎样的?

深入剖析HashMap:底层原理、扩容机制与线程安全 ​时空权衡:在哈希碰撞与空间占用间寻找平衡点 ​渐进式优化:从链表到红黑树的结构演进 ​并发控制哲学:从完全同步到分段锁再到细粒度锁自适应哈希算法:根据运行时数据特征动态调整哈希函数 ​无锁化探索:尝试将CAS机制扩展到更多操作场景内存布局优化:针对新一代CPU架构优化缓存行利用率通过深入理解HashMap的设计精髓,开发者不仅能更好地应对面试考察,更能将这些设计思想应用到自定义数据结构的开发中,写出高性能、高可靠的Java应用。 阅读详情

一、实现原理

1.1 哈希表
1.1.1 概述

要学习 hashmap,就不得不提哈希表。

维基百科定义如下:

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

在 java 中,保存数据有两种比较简单的数据结构:数组 和 链表。

  • 数组:寻址容易,插入和删除困难;
  • 链表:寻址困难,但插入和删除容易。

它是把数组和链表这两种结构结合在一起,发挥出各自的优势,这种结构就是哈希表。

img

1.1.2 哈希冲突

何为 hash 冲突,当我们对某个元素插入之后,发现该位置已经被其他元素占用了。这就是哈希碰撞,这种情况是不可避免的,比如著名的抽屉原理,在三个抽屉里面放入四个苹果,那么至少有一个抽屉不少于一个苹果。这就是冲突。

哈希冲突解决的方法有很多种,常见的有:

  • 开放定址法
  • 再散列法
  • 链地址法:hashmap 采用的此方法。

hashMap 正是基于哈希表的 map 接口的非同步实现。此实现提供了所有可选的映射操作,并允许使用 null 值和 null 键,但它不保证映射的顺序。

1.2 存储分析

hashmap 的主干是一个 Node 数组,里面存放了很多 Node 元素,Node 是 HashMap 的基本组成单元,每个 Node 包含一个key-value 的键值对。

    /**
      * 第一次使用初始化表,并将大小调整为必要的。分配时,长度总是2的幂
      */
     transient Node<K,V>[] table;  // transient 关键字是防止属性被序列化

这里面的每个小黑点就是 Node 节点。

image-20210702112738034

Node 它是 HashMap 中的一个 静态内部类,实现了 Map.Entry 接口,代码如下:

 /**
      * Basic hash bin node, used for most entries.  (See below for
      * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
      */
     static class Node<K,V> implements Map.Entry<K,V> {
         final int hash; // 用来定位数组索引位置
         final K key;
         V value;
         Node<K,V> next; // 链表的下一个node节点
 ​
         Node(int hash, K key, V value, Node<K,V> next) {
             this.hash = hash;
             this.key = key;
             this.value = value;
             this.next = next;
         }
 ​
         public final K getKey()        { return key; }
         public final V getValue()      { return value; }
         public final String toString() { return key + "=" + value; }
 ​
         public final int hashCode() {
             return Objects.hashCode(key) ^ Objects.hashCode(value);
         }
 ​
         public final V setValue(V newValue) {
             V oldValue = value;
             value = newValue;
             return oldValue;
         }
 ​
         public final boolean equals(Object o) {
             if (o == this)
                 return true;
             if (o instanceof Map.Entry) {
                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                 if (Objects.equals(key, e.getKey()) &&
                     Objects.equals(value, e.getValue()))
                     return true;
             }
             return false;
         }
     }
1.3 hash 算法

哈希桶的数组如果很大,那么分配的元素就会很分散,占用更多空间;如果哈希桶的数组很小,就会发生多次hash冲突。

如何控制空间更少 的同时碰撞过少,就需要让哈希桶的大小在一个合理的范围内,这就需要学习 Hash 算法。

hash 算法本质上分为三步:

  • 1、取 key 的 hashCode 值

    问题1:java中对象都已一个hashCode()方法,那为什么还需要hash函数呢?

    答:hashcode 返回值为int 类型且长度不定,为了满足理想的散列表数组大小,我们需要将 hashcode 值转换唯一定长的hash值。

  • 2、高位运算

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

即 key 如果是 null ,那么就放在 Node 数组的第一个位置,下标为0。

问题2:为什么计算hashcode 的时候要将hashCode 右移16位?

答:我们先通过一个例子来了解下这计算过程,需要注意的是异或是相同为0,不同为1.

 假设 h=   0010  0101  1010  1100  0011  1111  0010  1110
 ​
 右移16位  0000  0000  0000  0000  0010  0101  1010  1100
 ​
 异或结果: 0010  0101  1010  1100  0001  1010  1000  0010

分析:将 h 无符号右移16位,再与原来的h异或,相当于计算出来hashcode的值 结合了高位特征和低位特征,增加了hashcode 的随机性,在后续计算数组中的位置的时候更有参考性,这也是 设计者的一个细节。

1.5 get 方法源码分析

作者:潇雷
链接:https://juejin.cn/post/6996842378229186574
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 3、取模运算,计算出在数组中的位置。

     e = tab[index = (n - 1) & hash]
    

    问题3:为什么计算数组下标位置的时候,n必须为2的幂次方?

    答:因为是按位与运算,那就得保证低位都是1,才能计算出每个元素在数组中的位置,而2的n次方-1 刚好低 n 位全是1。

    为了能够让 HashMap 存取高效,尽量较少碰撞,也就是尽量把数据分配均匀。

    1.4 put 方法源码分析

    1、找到put 方法,里面调用了putVal ()方法

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

    2、hash 方法我们前面也分析过了,通过高16位和原来的 hashcode 进行异或运算得到一个理想的hashcode值。

    3、查看putVal ()方法

     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                        boolean evict) {
             /**
             * tab: node 数组
             * p  : 表示当前散列表的元素
             * n  : 表示散列表数组的长度
             * i  : 寻址下标
             */
             Node<K,V>[] tab; Node<K,V> p; int n, i;
         
             /**
             * 如果这个散列表没有创建,或者长度为0,则初始化
             */
             if ((tab = table) == null || (n = tab.length) == 0)
                 n = (tab = resize()).length;
         
             /**
             * 1、(n - 1) & hash 就是找当前要插入的数据应该在哈希表中的位置
             * 2、找出要插入的位置的元素,如果为null,就把 k-v的node节点放进去,
             */
             if ((p = tab[i = (n - 1) & hash]) == null)
                 tab[i] = newNode(hash, key, value, null);
             // 如果这个位置有数据,则进入 else
             else {
                 /**
                 * e: 声明一个节点
                 * k:表示临时的一个key
                 */
                 Node<K,V> e; K k;
                 
                 // 如果当前位置上的那个数据的 hash 和我们要插入的 hash 是一样,代表没有放错位置
                 if (p.hash == hash &&  
                 // 如果当前这个数据的 key 和我们要放的 key 是一样的,实际操作应该就是替换值
                     ((k = p.key) == key || (key != null && key.equals(k))))
                 // 将当前节点赋值给局部变量 e
                     e = p;
                 
                 // 如果当前节点的 key 和要插入的 key 不一样,然后要判断当前节点是不是一个红黑树类,如果是,就创建一个新节点把数据放进去
                 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;
                         }
                             // 如果当前遍历的数据和要插入的数据的key 是一样的,就把value替换
                         if (e.hash == hash &&
                             ((k = e.key) == key || (key != null && key.equals(k))))
                             break;
                         p = e;
                     }
                 }
                 // 如果当前的节点不等于空,就把当前节点的值赋值给 oldvalue
                 if (e != null) { // existing mapping for key
                     V oldValue = e.value;
                     if (!onlyIfAbsent || oldValue == null)
                         e.value = value; // 把当前要插入的 value 替换当前的节点里面值
                     afterNodeAccess(e);
                     return oldValue;
                 }
             }
             // 长度+1
             ++modCount;
             if (++size > threshold)
                 // 如果长度超过阈值,重新扩容
                 resize();
             afterNodeInsertion(evict);
             return null;
         }
    

    小结:

    JDK1.8 之前的 hashMap 插入是在链表的头部插入,而 JDK1.8 开始,就是采用了尾部插入。这一点将在第四小节 hashMap 的死循环中讲解。

    首先,hashMap 里面 的put 方法,是套了一个putVal 方法。

  • 1、put 方法会将传入的key进行 hash运算,然后在经过n-1 的与运算之后得到数组的下标位置。
  • 2、如果没有发生碰撞,就把value放到数组的当前下标位置
  • 3、如果发生了碰撞,且是在树节点下面,就遍历树节点,将值对比放进去
  • 4、如果是在链表,就遍历链表,将节点替换或插入,然后判断当前链表是否需要转成红黑树。
  • 5、最后,map的数值+1,判断当前长度是否超过阈值(容量* 负载因子),如果超过了,就扩容。
 public V get(Object key) {
     Node<K,V> e;
     return (e = getNode(hash(key), key)) == null ? null : e.value;
 }
 final Node<K,V> getNode(int hash, Object key) {
         // tab :引用当前hashMap 的散列表
         // first : 桶位中的头元素
         // e : 临时node元素
         // n : table 数组长度
         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
     
         if ((tab = table) != null && (n = tab.length) > 0 &&
             (first = tab[(n - 1) & hash]) != null) {
             
             // 情况1:定位出来的元素就是我们要get 的元素
             if (first.hash == hash && // always check first node
                 ((k = first.key) == key || (key != null && key.equals(k))))
                 return first;
             
             // 情况2:当前桶位有元素,链表或红黑树
             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;
     }
     

小结:

  • 1、如果数组不为null 且长度大于0,查找数组的第一个节点是否匹配,如果找到了就返回该桶内的值。
  • 2、如果没有找到,就去数组中元素的下一个节点的数据,分别遍历链表或红黑树。
1.6 总结

HashMap 是基于 Hash 算法实现的

  • 1、当我们往 hashmap 中 put 元素时,利用 key 的 hashcode 重新 hash 计算出当前对象的元素在数组中的下标

  • 2、存储时,如果出现 hash 值相同的key,此时有两种情况。

    • 1、如果key 相同,就覆盖原始值。
    • 2、如果key 不同,就将冲突的key对象放在链表中。
  • 3、获取时,直接找到 hash 值对应的下标,在进一步判断 key 是否相同,从而找到对应值。

  • 4、理解了以上过程就不难明白 hashMap 是如何解决hash冲突的,核心就是使用了数组的存储方式,然后将冲突的对象放入链表中,一旦发现冲突就在链表上做进一步对比。需要注意的是 jdk1.8之后,在链表长度超过8,总长度大于64之后,会将链表转成红黑树优化查询效率。

二、扩容机制

在扩容机制中,需要明确几个概念

 1、默认的初始化容量大小为16,必须为 2 的幂次方
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 ​
 2、最大的容量,在两个带参数的构造函数隐式指定更高值时使用,必须为 2的幂次方
    static final int MAXIMUM_CAPACITY = 1 << 30;
 ​
 3、默认的负载因数,这个值最好不要改动,这个值可以大于1.
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
   
 4、链表转红黑树的阈值:
    static final int TREEIFY_THRESHOLD = 8;
    
 5、红黑树退化成链表的阈值:
    static final int UNTREEIFY_THRESHOLD = 6;
    
 6、当哈希表的容量大于64时,才允许树化(链表转红黑树),如果小于64,则直接扩容,这个值不能小于4*TREEIFY_THRESHOLD。不然会进行选择扩容还是树化。
     static final int MIN_TREEIFY_CAPACITY = 64;
2.1 什么时候发生扩容

在jdk1.8中,当元素数量超过阈值(容量*负载因子)时,一般就会发生扩容,每次扩容的容量都是之前容量的2 倍数。

HashMap的容量是有上限的,必须小于1<<30。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE。

2.2 jdk1.8 中的扩容机制

为什么要扩容?

为了解决哈希冲突导致的链化影响查询效率的问题,扩容会缓解该问题。

扩容是一件很耗性能的操作,数组是无法自动扩容的,所以我们需要重新计算出新的容量,然后再创建新的数组,并将所有的元素拷贝到新的数组中,并释放旧数组的数据。

扩容数组之后为原来的两倍,(n-1)& hash 也就变成了(2n-1)& hash,所以在之前是因为后 n 位在同一个链表上的元素 ,现在就要与 n+1 位 & 运算,得出的第 n+1 位为0 或1 ,

造成两种结果:一种是没有影响还在当前数组下标位置;另一种是原位置+扩容长度。

扩容的 resize 算法如下:

 final Node<K,V>[] resize() {
         // oldTab : 引用之前的哈希表
         Node<K,V>[] oldTab = table;
         // oldCap : 表示扩容之前 table 数组的长度
         int oldCap = (oldTab == null) ? 0 : oldTab.length;
         // oldThr : 表示扩容之前的阈值
         int oldThr = threshold;
         // newCap :扩容之后的大小,newThr:扩容后的阈值
         int newCap, newThr = 0;
         
         // 条件如果成立,hashMap中的散列表已经初始化过了,是一次正常扩容
         if (oldCap > 0) {
             // 如果扩容值超过最大值,就没有扩容
             if (oldCap >= MAXIMUM_CAPACITY) {
                 threshold = Integer.MAX_VALUE;
                 return oldTab;
             }
             // oldCap 左移翻倍,并且赋值给 newCap,newCap 小于数组最大值限制,且扩容之前的阈值 >=16
             // 这种情况,下次扩容的阈值翻倍
             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                      oldCap >= DEFAULT_INITIAL_CAPACITY)
                 newThr = oldThr << 1; // double threshold
         }
         // oldCap == 0 ,说明hashmap中的散列表是 null,对应三种构造函数
         else if (oldThr > 0) // initial capacity was placed in threshold
             newCap = oldThr;
      
         // oldThr ==0 oldCap ==0 对应构造函数 new hashMap()
         else {               // zero initial threshold signifies using defaults
             newCap = DEFAULT_INITIAL_CAPACITY;
             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
         }
         
         // newThr 为零时,通过 newCap 和负载因素 计算出一个新阈值。
         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;
      
         // 说明,hashmap 本次扩容之前,table不为null
         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;
     }

在putval()中,我们看到在这个函数里面使用了2 次 resize()方法,resize()方法表示的在进行第一次初始化的时候会进行扩容。jdk1.8 之中做的优化就是对同一个桶的位置的进行判断,该元素的位置要么停留在原始位置,要么停止在原始位置+数组大小位置上。而1.7是重新计算hash值,根据hash 值对其进行分发。

扩容小结:

  • 在 jdk1.8 中 ,resize 方法 是在 hashmap 中的键值对大于阈值或者初始化时,就调用 resize 方法进行扩容
  • 在每次扩容的时候,都是扩展 2倍
  • 在扩展后 Node 对象的位置要么在原位置,要么移动到原偏移量两倍的位置
  • 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
  • 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
高精度计算Π的值(C语言) 题目要求 限制使用双向链表作存储结构,请根据用户输入的一个整数(该整数表示精确到小数点后的位数,可能要求精确到小数点后500位),高精度计算PI值。可以利用反三角函数幂级展开式来进行计算。 输入5 输出3.14159 思路分析 反三角函数幂级展开式 由公式可以看出,只要计算出来每一个R(n)的值再相加取二倍就可以得到Π的值。 经过推算,要是令R(1)=2,只需要将每个R(n),相加即可。 因... 阅读详情

相关推荐

开源大模型“去审查化”技术解析:从LLaMA架构到本地部署与伦理边界

大型语言模型(LLM)基于Transformer架构,通过海量数据训练获得理解和生成自然语言的能力。其核心原理在于自注意力机制对上下文关系的建模。为了确保输出内容的安全与合规,主流模型普遍采用基于人类反馈的强化学习(RLHF)等技术进行安全对齐。这一过程旨在使模型行为符合人类价值观,是AI伦理工程实践的关键环节。然而,在特定研究场景下,如对抗性测试或内容安全算法研究,技术人员需要模拟极端对话以评估系统鲁棒性,这催生了针对“去对齐”或“移除护栏”的技术探索。此类技术通常通过对预训练模型进行反向微调或继续训练来

weixin_33743880的博客 595

关于HashMap扩容机制

HashMap的底层有数组 + 链表(红黑树)组成,数组的大小可以在构造方法时设置,默认大小为16,数组中每一个元素就是一个链表,jdk7之前链表中的元素采用头插法插入元素,jdk8之后采用尾插法插入元素,由于插入的元素越来越多,查找效率就变低了,所以满足某种条件时,链表会转换成红黑树。随着元素的增加,HashMap的数组会频繁扩容,如果构造时不赋予加载因子默认值,那么负载因子默认值为0.75,数组扩容的情况如下: 1:当添加某个元素后,数组的总的添加元素数大于了 数组长度 * 0.75(默认,也可自己设

zhuzhianing的博客 5万+

orange软件:入门使用

orange做数据分析契机实验手册目录orange软件部分界面展示小试牛刀:数据导入和展示小试牛刀:散点图绘制总结 契机 今天在上大数据时代实验课,作为一名软件工程专业的大三学生,在上这节公选课之前就决定这节课做完这个学期所有的实验报告,可以说“有亿点”不屑;尤其当老师说Python很麻烦的时候,我更是震惊了,因为我接触过这么多编程语言,Python真的很友好了,很傻瓜,但是当老师打开实验手册的时候,我真香了!!!下面先来看看手册的目录: 实验手册目录 为什么会真香呢,因为目录里这些有好多机器学习算法!

weixin_45880844的博客 1万+

简述HashMap的扩容机制

resize这个方法是HashMap的扩容方法。HashMap在扩容时,都是翻两倍,比如16的容量扩大到32,。HashMap进行扩容的方法是比较巧妙的,扩容后,与原来的下标(n-1)&hash相对,其实只是多了1bit位。扩容后节点要么是在原来位置,听起来好像很懵,所以还是认真看下面的分析:下面给出例子,比如从容量为16扩容到32时,画图表示:进行扩容,扩大到原来的两倍:到这一步,下标,扩容后的数据10101和原来的00101相比,其实就是多了1bit,10101。

Nicky's blog 7020

我用C#实现上位机数据一键导出Excel:从乱码到百万行数据秒级导出的实战技巧

ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("日报表");// 1. 标题(合并单元格)worksheet.Cells["A1"].Value = $"产线日报表 -DateTimeNowToString// 2. 表头和数据(同上)worksheet.Cells["A3"].Value = "时间";// ...省略表头和数据填充// 3. 合计行(公式计算)

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 713

【UG\NX二次开发】通过查看自己的dll,找内部函数的方法

NX二次开发-群精选」专栏收录了那些曾散落在聊天对话框里的精彩的知识分享,这都是被遗落的宝贵财富。让我们一起向大佬学习,充满激情地进步,不断超越自己。底部联系方式,欢迎您加入一起交流。​下次一定。

王牌飞行员_里海的博客 443

HashMap的扩容机制

HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据因为在查询上使用散列码(通过键生成一个数字作为数组下标,这个数字就是hash code)所以在查询上的访问速度比较快,HashMap最多允许一对键值对的Key为Null,允许多对键值对的value为Null。它是非线程安全的。在排序上面是无序的。

wming666的博客 5563

【2026年华为暑期实习(AI)-4月8日-第一题- 选择题】(题目+思路+Java&C++&Python解析+在线测试)

答案与解析 1. 相关系数问题 答案:C. 完全负线性相关 解析:相关系数ρ=-1表示X和Y之间存在完全负线性相关关系,所有数据点都严格落在一条斜率为负的直线上。 2. 矩阵秩问题 答案:B. Σ的对角线上非零奇异值的个数 解析:矩阵A的秩等于其奇异值矩阵Σ中非零奇异值的个数,因为U和V都是正交矩阵,不改变矩阵的秩。 3. 条件概率问题 答案:B. 0.5 解析:根据全概率公式和贝叶斯公式计算,P(B)=0.5×0.4+0.3×0.6=0.38,P(A|B)=(0.5×0.4)/0.38≈0.526,最接近

m0_73659489的博客 287

Hashmap实现原理及扩容机制详解

目录 HashMap基础 HashMap实现原理 Node和Node链 拉链法 关于Node数组 table 散列算法 HashMap和红黑树 关于TreeNode 红黑树基础 HashMap扩容机制 JDK1.7下的扩容机制 JDK1.8下的扩容机制 HashMap基础 HashMap继承了AbstractMap类,实现了Map,Cloneable,Serializa...

lkforce 12万+

STM32(HAL库)驱动OLED

通过STM32F103C8T6单片机采用HAL库方式对0.96寸OLED(IIC通讯)屏幕进行驱动。

weixin_44597885的博客 8806

AI Agent智能体在Android开发中的应用

本文探讨了AIAgent(智能代理体)在Android开发中的应用,涵盖技术原理、实践案例与未来趋势。AIAgent通过机器学习、自然语言处理等技术实现代码生成、测试自动化及性能优化,显著提升开发效率。例如,强化学习模型可优化APP响应时间(公式:$\text{响应时间}=f(\text{负载},\text{资源})$),自动化测试覆盖率提升至95%。

郑伟强dev的专栏 125

HashMap底层实现原理 扩容机制

HashMap概述: HashMap基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null 值, 因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。 HashMap的容量,默认是16 /** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_C

m0_52384288的博客 417

基于Simulink的电气弹簧ES稳压控制模型仿真

在本文中,我们将使用Simulink建立一个ES稳压控制系统的模型,并通过仿真验证其性能。接下来,我们配置了各个组件的参数,包括电源电压、负载电流以及ES和控制器的传递函数。通过调整模型中的参数和控制算法,我们可以进一步优化系统的性能并实现更好的电压稳定调节。该系统通过对电力系统中的ES进行控制,调整其阻尼特性,以实现对电压的稳定调节。例如,我们可以设置电源的电压值、负载的电流值,以及控制器的参数(如PID控制器的比例、积分和微分增益)。反馈控制可以根据测量值来调整控制器的输出,从而实现对电压的稳定调节。

DevProZ的博客 423

HashMap实现原理和扩容机制

HashMap实现原理和扩容机制 1.实现原理: HashMap的底层实现是一个哈希表即数组+链表; HashMap初始容量大小16,扩容因子为0.75,扩容倍数为2; HashMap本质是一个一定长度的数组,数组中存放的是链表。 当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry中,如...

qq_39581763的博客 1081

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

HashMap实现原理及扩容机制

HashMap实现原理及扩容机制HashMap基础HashMap基础HashMap实现原理Node和Node链拉链法关于Node数组 table散列算法HashMap和红黑树关于TreeNode红黑树基础HashMap扩容机制JDK1.7下的扩容机制JDK1.8下的扩容机制 参考:添加链接描述 HashMap基础 HashMap继承了AbstractMap类,实现了Map,Cloneable,Serializable接口 HashMap基础 HashMap的容量,默认是16 /** * T

HenryPoP的博客 644

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

HashMap实现原理 | HashMap扩容机制 | 底层源码

本文就构造方法、成员变量,以及添加元素三个方面,对HashMap的源代码进行深入探究。探索HashMap扩容机制的奥秘。

m0_62467665的博客 2940

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

HashMap底层实现原理及扩容过程

目录 一、hash函数实现 二、HashMap内部哈希数组的长度为什么要取2的幂 三、哈希数组的扩容 1、什么时候触发扩容? 2、扩容做什么事情? 1)新hash数组初始化 2)已存储键值对重新散列 四、HashMap存储原理 1、put操作 1)hash数组索引位置为null 2)索引位置第一个节点key与要存储的键值对key相等 3)要存储的位置已经是树节点 4)普通节点 2、get操作 五、与JDK1.7的差异 Map存储键值对,并且实现方式相对于List来说要复杂很多

qq_43568704的博客 3012

HashMap底层实现原理和扩容机制是什么?

当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry中,如果这个位置上面已经有元素了,那么就将新加入的元素放在链表的头上,最先加入的元素在链表尾。关于扩容机制,HashMap初始容量大小为16,扩容因子为0.75,扩容倍数为2。扩容会导致元素重新哈希并重新放置到新的数组中,这个过程可能会导致元素顺序的改变。HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。

3万+

Java Hashmap实现原理及扩容机制详解

HashMap基础 HashMap继承了AbstractMap类,实现了Map,Cloneable,Serializable接口 HashMap的容量,默认是16 /** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_C...

skykingf的专栏 1711

Java集合-HashMap详解(底层数据结构、实现原理、扩容机制)

Java集合-HashMap详解,主要讲讲HashMap的底层数据结构、实现原理、扩容机制和负载因子,面试Java集合专项必备。

m0_73500410的博客 1931

就业:HashMap实现原理和扩容机制

1.实现原理: HashMap的底层实现是一个哈希表即数组+链表; HashMap初始容量大小16,扩容因子为0.75,扩容倍数为2; HashMap本质是一个一定长度的数组,数组中存放的是链表。 当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry中,如果这个位置上面已经有元素了,那么就将...

xiaoyaGrace的博客 229

HashMap的实现原理,HashMap方法详解,hash()计算的原理,扩容机制

HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap基于hashing原理,我们通过put()和get()

weixin_44797327的博客 902

HashMap的底层实现原理+扩容机制+红黑树(精炼)

HashMap的底层实现原理及其扩容机制(精炼)一、HashMap的数据结构二、HashMap 键值对的存储原理和方式:三、HashMap的扩容机制Java 7 中Hashmap扩容机制Java8中HashMap扩容机制 一、HashMap的数据结构 1.它属于常用的数据结构 其中三大常用数据结构是 :数组结构、链表结构、哈希表结构。 2.优点:哈希表结构结合了 数组结构和链表结构的两大优点。添加删除快,查询也快。 二、HashMap 键值对的存储原理和方式: 1.HashMap.Node <

CKApril_java的博客 1万+
上一篇: 深入浅出kafka【3】消费者客户端详解
下一篇: Java里你为什么不用这种优雅的方式封装处理和抛出异常?
加班不秃顶
博客等级 码龄7年 1822粉丝 599原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值