JDK1.8 HashMap 学习笔记

JDK1.8HashMap底层实现原理 hashmap是我们开发中用的最常见的集合之一,但是我们是否有了解过他的底层呢?这篇文章我将带您了解一下hashmap的底层 位运算 在学习hashmap底层原理的时候,我们必须要掌握位运算,别问为什么,往下面看自然知晓 ... 阅读详情

前言


  本文主要根据jdk1.8的HashMap实现做的一次学习笔记,jdk1.7的HashMap与jdk1.8的实现有一些不同,但大体原理也是一致的。

HashMap实现原理


   Map主要应用的场景是Key-Value键值对的存储,如果当Key-Value键值对的数量达到一定数量的时候,必然会影响我们的查询效率,而大家使用HashMap的时候并没有显著的感觉查询效率显著的下降,主要是HashMap的实现方式。通常我们再进行数据查找的时候都是进行Key比对的方式查找,** 这样带来的问题就是如果有100w个Key那么就要查找100w次(最坏情况)。而hashMap的查找方式是将100w个数据分成了N个数据桶,将带有某些特征的数据放到一个桶里(Hash散列), 那么100w个数据分散到每一个数据桶里就成了100w/N个数据(分布均匀的情况下),查找次数就缩减到了100w/N次(最坏情况) **。

如下所示

  • 数组方式
      arr[]:
1
2
3
4
5
6
7
8
9
  • Hash方式
    map:
    {
       arr[0]:
1
2
3

          arr[1]:

4
5
6

          arr[1]:

7
8
9

        }
像arr[0],arr[1],arr[2]我们将他们称为哈希槽,每个哈希槽里面是一个链表结构
在这里也要说明下:HashMap的Hash槽并不是初始化之后数量就不会变化了,HashMap的Hash槽会根据Map中元素数量的变化而进行调整(目前只会变大),这里有一个结论就是相同数据且均匀分布的情况下Hash槽的数量越多查询效率越高

HashMap内部结构


   刚才已经粗略的简绍了HashMap的存储结构, 其实jdk1.8的HashMap在arr[x]的位置上使用两种数据结构:** 单向链表和红黑树 **,这里在所属哈希槽数据量不大的时候会使用单向链表,默认每个哈希槽数量大于8的时候会转成红黑数的形式存储,在小于6的时候会有红黑树转换成单向链表(如果是红黑树的情况下)。

整体结构

粗略(因为位置问题,有一定的错误(hash槽最少也是16个,红黑树至少8个节点以上)),但是大体如下
HashMap简化结构图

每个Hash槽中元素结构

这里每个元素在jdk1.8中抽象为Node, jdk 1.7中是HashMap.Entry.
下图是jdk1.8 Node的实现(简写省略了很多方法):

class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        ...
}

红黑树的Node是Node的子类 TreeNode

class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        ...
}

HashMap属性说明


    
    /**
    * Hash槽
    */
    transient Node<K,V>[] table;

  	/**
     * 默认Hash槽的大小。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

	/**
     * table的扩容阈值,当table的Node数量大于此值就会进行扩容
     */
 	int threshold;	

    /**
     * Hash可扩容的最大值。
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 负载因子,这里我们上文说过Hash越多查询效率就越高但是也会带来一个问题,空间上会有一些浪费,负载因子的作用就是在空间和时间上做出一个更好的权衡,这里通常不需要更改,经过测试0.75是通常情况下比较理想的取值了。
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    final float loadFactor;
    
    /**
  	 * 当Hash槽中的元素数量,大于了此值将会把链表结构转换成红黑树结构。
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 当Hash槽中的元素是以红黑树结构,当树的节点小于此值将会将红黑树转变成链表。
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 这里是转换成红黑树对于Map元素的总量限制,如果Map元素的总量小于此值,当进行链表转红黑树的时候只会进行一次resize()扩容。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

	/**
	* 每次修改Map时会进行modCount++操作,用于快速检查是否在迭代时有其他线程是否修改过Map元素
	*/
	transient int modCount;

这里大家有没有注意到一个小细节,就是他的很多默认值都是2的次幂,这里我先把原因告诉大家:主要是因为HashMap的hash方法需要保证尽量减少Hash数量对于hash值得影响,以及HashMap扩容时移动Node的便利。

HashMap方法说明


  • 构造方法
    1. public HashMap(int initialCapacity, float loadFactor)
      可以预先设置Hash槽数量和负载因子,这里如果预测Map要存大量数据时可以将initialCapacity设置的高些,不然移动元素会比较耗时。
    2. public HashMap(int initialCapacity)
    3. public HashMap()
    4. public HashMap(Map<? extends K, ? extends V> m)
  • hash方法
    hash方法主要是获得散列值,以确保key的均匀分布。
      static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }
    
    HashMap是根据hash方法返回的hash值来和(hash槽的数量-1)进行与(&)操作,HashMap为了消除hash槽数量对hash值的影响使每一次扩容的hash槽数量都是2的次幂(最低是16) 。这里为什么2的次幂可以减少影响呢?是因为2的次幂-1,16在二进制表示的话是(0000 1111)与运算是都为1的情况下结果才为1,那面最后影响结果的是hash方法获得的hash值。
    这里大家可能会有一些疑惑为什么要把得出的hash值用后16位异或前16位,这里是因为我们Hash槽通常数量都达不到高16位的数量,很多时候hash值得前16位并没有参加到hash槽定位的运算中,所以这里进行了一次高位与低位的运算来提高hash函数的分散性,以提高hash查找的效率,这里也叫扰动函数
    hash函数这里使用的是Object的hashCode方法,应该是属于加法hash。
    几种常见的hash函数:加法hash,乘法hash,除法hash。
  • tableSizeFor方法
    tableSizeFor方法主要用于保证Hash槽的数量是2的次幂。
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;
    }

这个方法主要是确保用户初始化HashMap时传进的initialCapacity是2的次幂,即使初始化的时候调用了initialCapacity(30),HashMap也会根据此方法来获取initialCapacity向上最接近的2的次幂,至于为什么要保持2的次幂请参考hash方法,以及put方法和reszie方法。

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

	/**
	* onlyIfAbsent : 表示是否可以替换已存在的元素(true不可以,false可以)
	* 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;
	        if ((tab = table) == null || (n = tab.length) == 0)
	            n = (tab = resize()).length; //判断hash槽是否初始化,如果没初始化则先调用resize方法
	        if ((p = tab[i = (n - 1) & hash]) == null)  //确定Hash槽位置,使用hash槽数量-1 与 hash值
	            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) //如果此Hash槽的元素是红黑树结构则使用红黑树的put方法(具体暂时忽略)
	                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) //如果大于指定的红黑树阈值,则将此Hash槽的Node转换成红黑树的形式
	                            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; //增加乐观锁,这个主要用于迭代时,其他线程修改了Map时的快速失败
	        if (++size > threshold) //判断是否需要扩容
	            resize();
	        afterNodeInsertion(evict); //回调,可以重写此方法监听
	        return null;
	    }

这里put流程可以参考后面的存储流程图
这里主要的就是key的Hash槽定位(n - 1) & hash,要理解为什么要与hash值?为了定位Hash槽,(n-1)与上任何数都是小于n-1的,并且n是2的次幂的话,结果的值就只跟hash的值有关。

  • resize方法
    resize方法主要负责hash槽的初始化,扩容,以及node元素在扩容后的迁移
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//查看Hash槽是否初始化
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) { //已初始化的情况
            if (oldCap >= MAXIMUM_CAPACITY) { //判断hash槽扩容后是否超过了最大值,若超过就不在进行扩容,并且调整阈值。
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // 获得新的hash槽的数量值和新的hash槽阈值
        }
        else if (oldThr > 0) // 初始化用于HashMap(Map map)构造方法的情况
            newCap = oldThr;
        else {               // 初始化赋予HashMap默认值
            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) {//扩容前Hash槽内数据不为空的情况下需要移动Node节点。
            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 {
                    //链表的Node数据的Hash转移。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //判断扩容后是否需要移动,扩容前是hash值 * (n-1),由于n是2的次幂,那么扩容后Node所属hash槽的位置只能是不变或者加之前的n-1
                            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;
    }

这里可以参考后面的扩容流程图
resize方法比较有困惑的地方就是(e.hash & oldCap) == 0这行代码,这里主要是因为n是2的次幂
举个例子:
假如之前是16个Hash槽 二进制表示是:0001 0000。 n-1则为: 0000 1111
假设这个key的hash值为 0010 1110 那么和n-1 & 的值为 0000 1110。
下面我们把Hash槽扩容为32 二进制表示是: 0010 0000。 n-1则为:0001 1111
那么key的hash值 & n-1 的值为 0000 1110。 这里只要计算二进制的第5位就可以了,因为后面的数字n-1不变,hash值的二进制也不会变。之前是由于n-1只有4位有效数字,现在扩容后n-1有5位有效数字,所以我们只要把hash值的第五位加进来参与运算即可。
这里获得的Hash槽位置只会是不变或者是之前的位置加上(old n-1)。

HashMap存储流程


Created with Raphaël 2.2.0 传入参数Key,Value 计算Key的Hash值 哈希槽是否已经初始化? 根据Hash值确定具体的Hash槽位置 该哈希槽是否有元素? 当前Node节点的Key 是否与参数Key相等? 将新的value值复制给当前Node节点 当前容量是否需要扩容? 对Hash槽进行扩容 完成 当前Node节点是否 是红黑树节点? 使用红黑树的插入节点方式 插入新的Key,Value 轮询当前Node节点的下一个节点, 如果Key像等则替换Value值, 如果不相等且是最末尾节点的话添加新的Node节点。 当前链表长度是否要 转成红黑树结构? 将链表转换成红黑树 生成一个新的Node节点 添加到Hash槽中 yes no yes no yes no yes no yes no yes no

HashMap扩容流程


Created with Raphaël 2.2.0 获得当前table容量和table提升阈值 当前table容量大于0? 当前table容量是否是最大? 1 1 1 1 完成 扩大table容量值为之前 的2倍,提升阈值为之前的2倍 新的阈值是否为0? 新的table容量值乘以 负载因子获得新的阈值 当前table的node数量大于0? 获得老的Hash槽中的下一个元素 是否还拥有下一个Hash 扩容前的节点? 当前Hash槽是否为空? 当前节点是否是红黑树节点? 红黑树移动操作(暂时省略) 或得链表下一个Node 是否还拥有下一个 Node节点? 节点是否需要移动? 移动Node到(hash槽+老的Hash槽数量) 的hash节点上 当前阈值大于0 ? 将当前阈值赋值给新的table容量值, 这个情况的产生是通过调用HashMap(Map map)的方法产生的 初始化table容量值和阈值为默认值。 yes no yes no yes yes no yes no yes no yes no yes no yes no yes no

写在最后

这篇文章主要是针对HashMap源码解析后的笔记。
学习之后的收获是:

  1. hashMap对于确定Hash槽位置的整体布局与操作。
  2. hashMap整体结构的了解。
  3. hashMap的性能调整(可以基于初始Hash槽和加载因子)。

如果有什么错误,欢迎大家及时指正。

HashMap的扩容机制:JDK1.7,JDK1.8 参考文章: http://www.importnew.com/20386.html https://blog.csdn.net/z69183787/article/details/64920074?locationNum=15&amp;fps=1 以前看HashMap的时候,resize扩容部分没有仔细看,今天挤出时间,看之。 1. 确定hash桶数组的索引位置 我们当然希望这个Hash... 阅读详情

相关推荐

ARCGIS绘制全国各省PM2.5浓度图操作步骤

ArcGIS新手小白一枚,经常需要帮助老师绘制一些简单的空间图,利用ArcGIS绘制全国各省的pm2.5浓度图,简单又方便,不记下来会忘记~

weixin_43458484的博客 1万+

JDK1.8源码逐字逐句带你理解HashMap底层(2)

引言:很开心,大家继续来看HashMap底层的第二段。昨天(上一篇博文)我们主要是介绍了HashMap类的一些重要的成员变量并简述了他们的名称作用,附带图文解释了他们之间存在的关系,又深入学习了HashMap存储的发展和结构,以一个简单的demo描述了HashMap的初始化和各个变量的变化情况。今天主要是从HashMap的源码入手,我会逐字逐句的解释每一种情况中HashMap干了些什么。

brickworkers的博客 3778

带你学习Android中的ContentObserver

通过使用,开发者可以有效地监听系统设置的变化,例如屏幕亮度等。这种机制使得应用能够动态响应系统设置的更改,提升用户体验。在实现需要与系统设置交互的应用时,合理使用是非常重要的。

执剑人 1311

jdk1.8 HashMap工作原理和扩容机制(源码解析)

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

青元子的博客 9万+

Unity轻量级框架QFramework:四层架构与命令事件驱动的实战指南

在Unity游戏开发中,软件架构是确保项目可维护性和可扩展性的核心。通过分层架构(如MVC、DDD)和事件驱动设计,开发者能够有效管理代码复杂度,实现模块间的解耦与高效通信。QFramework作为一个轻量级框架,精准地运用了这些原理,通过严格定义的四层架构(表现层、系统层、数据层、工具层)和命令/事件通信规则,为中小型项目提供了“约束下的自由”。其核心价值在于以极简的代码(不足1000行)强制实施清晰的职责分离,如使用命令模式集中管理状态变更,并通过事件驱动实现响应式更新。这种设计特别适用于解决项目规模扩

weixin_42521032的博客 262

对角两对双差速轮AGV的平移、直行、转弯和原地旋转之PID控制

本示例将展示如何使用PID控制算法,控制具有对角线布置的两对双差速轮(左前和右后)的AGV,实现平移、直行、转弯和原地旋转等运动。通过对车辆的运动学和PID控制器的设计,我们可以精确地控制AGV的运动

xiaoc100200的博客 1510

重新认识HashMap(jdk1.8新增特性)

1.背景: HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、Linke...

hqy1719239337的博客 1万+

TDS水质检测传感器原理图设计,已量产(液体传感器)

最近调试了一款针对生活用水、水培系统的 TDS 水质检测模块 —— 毕竟 TDS(总溶解固体)值直接关联水的洁净程度,是这类场景里既实用又易获取的水质指标。下面拆解下这个电路的设计逻辑,说说每个部分是怎么配合实现精准检测的。

不脱发的程序猿 1538

jdk1.8 中HashMap源码学习笔记

目录 1. 写在前面 2. 源码分析 2.1.常量定义 2.2.常量定义 2.3.属性 2.4.构造方法 2.5.tableSizeFor()方法 2.6.put()方法 2.7.get()方法 2.8.hash()方法 2.9.resize()扩容方法 2.10.树形化方法treeifyBin() 2.11.remove()方法 3. 线程安...

czw_xiaobei的博客 231

Android Input——输入事件ANR执行(十六)

上一篇文章我们介绍了输入事件 ANR 的触发流程,这里我们再来看一下输入事件 ANR的执行流程。

小旭的专栏 944

自然语言处理在智能客服系统中的应用

引言与背景 1.1 书籍的目的和内容概述 《自然语言处理在智能客服系统中的应用》旨在探讨自然语言处理(NLP)技术在智能客服系统中的应用,帮助读者理解NLP的核心概念及其在解决实际业务问题中的关键作用。随着人工智能技术的快速发展,自然语言处理成为构建智能客服系统的核心模块之一。本书将深入解析NLP

AI天才研究院 966

JDK1.8HashMap源码学习笔记

JDK1.8HashMap源码学习笔记一、HashMap核心属性分析(threshold,loadFactory,size,modCount)二、构造方法分析三、HashMap put方法分析=>putVal方法分析四、HashMap resize扩容方法分析(核心)五、HashMap get方法分析六、HashMap remove方法分析七、HashMap replace方法分析八、其他为什么数组容量是二的次方(扩容为什么是扩两倍)?为什么负载因子默认是0.75? 一、HashMap核心属性分析(t

qq_56892136的博客 764

YOLOv11改进 - 卷积Conv | PATConv(Partial Attention Convolution)部分注意力卷积,在减少计算量的同时融合卷积与注意力的优势 | AAAI 2026

本文提出部分注意力卷积(PATConv)机制,并将其集成到YOLOv11中。传统神经网络中,卷积计算密集,注意力机制全局计算冗余,此前的“部分卷积”会丢失未计算通道的特征价值。PATConv通过“通道拆分 - 并行处理 - 结果拼接”的逻辑,给不同通道分配“擅长的任务”,兼顾局部与全局特征。基于此,它还衍生出PAT_ch、PAT_sp和PAT_sf三种细分模块。此外,还提出了动态部分卷积(DPConv),并构建了新的混合网络家族PartialNet。我们将PATConv代码集成到YOLOv11中,实验表明,

魔改工程师的博客 1085

HashMap源码学习笔记(jdk1.8)

注释很详细,一定能看懂 给大家一个建议,可以通过对源码打断点debug进行学习,还有源码中的注释真的很重要 本文主要讲述HashMap的get、put、resize方法、HashMap与HashTable的区别、HashMap1.7和1.8的区别。 首先看一下HashMap的基础属性,接着学习一下hashMap的get、put以及resize方法。 基础属性: // 默认的初始...

Hollake的博客 364

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

基于jdk1.8的HashMap源码学习笔记

作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧。所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员这种开源精神。(好吧,第一篇博客有点紧张) 一. HashMap结构 HashMap在jdk1.6版本采用数组+链表的存储方式,但是到1.8版本时采用了数组+链表/红黑树的...

weixin_30613343的博客 161

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

HashMap学习笔记,比较JDK1.7/1.8的区别

问题1:1.7和1.8各自如何让链表数据散列的更均匀,即如何重新计算hash值 问题2:如何获取新加元素在数组中的下标,用什么方法实现? 问题3:在扩容方面,一般情况下数据量大于当前容量*填装因子时,会采取扩容。jdk1.7有什么额外的判断?例如key从1-100,初始容量为16,为何key在12的时候没有去扩容,等于17的时候才去进行扩容。第二次扩容是在什么时候?前两次扩容有什么区别? 问...

weixin_42242029的博客 774

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

JDK1.8HashMap源码解析

本篇是JDK1.8的HashMap源码个人学习笔记,从属性&内部类,构造方法,增删查方法的角度,对HashMap进行简要分析,其中红黑树部分会在下一篇笔记中记录。

西岭千秋雪的博客 863

JDK1.7和JDK1.8中HashMap为什么是线程不安全的?

前言 只要是对于集合有一定了解的一定都知道HashMap是线程不安全的,我们应该使用ConcurrentHashMap。但是为什么HashMap是线程不安全的呢,之前面试的时候也遇到到这样的问题,但是当时只停留在***知道是***的层面上,并没有深入理解***为什么是***。于是今天重温一个HashMap线程不安全的这个问题。 首先需要强调一点,HashMap的线程不安全体现在会造成死循环、数据丢...

张先森的博客 7万+

jdk1.7与jdk1.8中HashMap区别(面试最详细版)

一、区别 1. 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构; 2. jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容; 3. 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插; 4. jdk1.7中的hash函数对哈希值的计算直接使用key的h...

PANDA博客 2万+

网易面试:JDK1.8将HashMap 头插法 改 尾插法,为何?

HashMap是Java中的一种基于哈希表实现的,它允许我们使用键值对的形式来存储和获取数据。从根本上来说,一个哈希表包含一个数组,但是元素访问不是通过 index 编号的形式(比如 array[i]的形式),而是通过特殊的关键码(也就是key)来访问数组中的元素。存放Value的时候,通过一个哈希函数,通过 **关键码(key)**进行哈希运算得到哈希值,然后得到映射到(map到)的位置, 去存放值 ,读取Value的时候,也是通过同一个哈希函数。

架构师尼恩 4933

八、JDK1.8中HashMap扩容机制

导读 前面文章一、深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二、Jdk1.7和1.8中HashMap数据结构及源码分析 、三、JDK1.7和1.8HashMap数据结构及源码分析-续 中我们分别对JDK1.7和JDK1.8中HashMap的数据结构、主要声明变量、构造函数、HashMap的put操作方法做了深入的讲解和源码分析。 四、深入理解Java中的HashMap「网易面试快答」文章中主要针对面试中常见的面试问题进行简单解答。 五、深入理解JDK1.7中H

不吃_花椒的博客 8419

【Java】HashMap原理-JDK1.7与JDK1.8的区别

一、HashMap 扩容 JDK1.7 和JDK1.8 扩容原理相同 HashMap初始化大小为16, 负载因子为0.75,每次当容量大于16 * 0.75 时, 进行扩容,扩容为原来的两倍。 JDK1.7: 扩容后需要重新计算hashcode值。 JDK1.8:扩容后无需重新计算hashcode值。(具体看后面“HashMap的容量为2的幂次方”部分) 二、HashMap的冲突解决 JDK1.7以前 HashMap使用 数组 + 链表的方式来解决哈希冲突,并使用头插法。 使用头插法的原因:一般情况下

qq_39480875的博客 2673

HashMap Jdk8的实现原理

一、Map的整体结构概要 上述为HashMap的内容结构,方法包含大部分常用的,没有列举完。 二、HashMap结构图 1、JDK7及之前 2、JKD8及之后 由上面结构图可知,HashMap由数组和链表共同构成,JDK8对HashMap做了优化,采用了红黑树的数据结构来做优化,使得HashMap存取速度更快。 三、Ha...

goosson的博客 7569

深入解析 HashMap:从 JDK 1.7 到 JDK 1.8 的变化及性能优化

HashMap是一个功能强大且高效的键值对存储类,它采用了哈希表结构和链表法解决哈希冲突。随着 JDK 1.8 的发布,HashMap引入了红黑树的支持,提升了大数据量下的性能。在实际开发中,HashMap适用于单线程或低并发的场景,对于高并发的情况,可以考虑使用。了解HashMap的实现原理,能帮助开发者在性能调优和设计时做出更合理的选择。🌟 关注我的优快云博客,收获更多技术干货!🌟。

dm菜鸟编程的专栏 1523

HashMap在JDK1.8和JDK1.7的区别(详解)

HashMap在JDK1.8和JDK1.7的区别 结论 先说结论,HashMap在1.7和1.8中最大的区别就是底层数据结构的变化,在1.7中HashMap采用的底层数据结构是数组+链表的形式,而在1.8中HashMap采用的是数组+链表+红黑树的数据结构(当链表长度大于8且数组长度大于等于64时链表会转成红黑树,当长度低于6时红黑树又会转成链表),红黑树是一种平衡二叉搜索树,它能通过左旋、右旋、变色保持树的平衡,关于红黑树大家想了解的可以自行百度,这里不再讲述。之所以用红黑树是因为他能够大大提高查找效率,

勤奋的派大星的博客 4122
上一篇: CenterOS 7.4 离线安装Docker 18.03.1
下一篇: JAVA volatile修饰符的作用
hellopeng1
博客等级 码龄10年 66粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值