【JAVA学习之路 | 进阶篇】HashMap源码剖析

简介: 【JAVA学习之路 | 进阶篇】HashMap源码剖析

1.JDK7版本创建与添加数据的的过程

(1). HashMap<String, Integer> map =new HashMap<>();


//创建对象过程中,底层会初始化数组Entry[] table =new Object[16];16是2的倍数.


...


map.put("hexua", 66);


将"hexua"和66封装到一个Entry对象entry中.并将此对象添加到table数组中.

(2). 添加修改的过程.

将(key1, value1)添加到当前的map中
首先需要调用key1所在类的hashCode()方法,计算key1对象的哈希值1(往往是根据key1中的属性计算的), 此哈希值再经过hash()方法后,得到哈希值2,哈希值2再经过indexFor方法后,确定了(key1, value1)在table数组中的索引位置i;
    |----> 如果此索引位置上没有元素(table[i] == null), 则(key1, value1)添加成功
    |---->如果此索引位置上已有元素(key2, value2), 则需要比较key2,key1的哈希值2是否相等.
          |---->如果二者的哈希值2不相等,则(key1, value1)添加成功
          |---->如果二者还相等,则继续比较二者的equals();调用key1的equals,key2作为参数传入
                |---->如果equals返回flase,添加成功
                |---->返回true,说明key1,key2是相同的,默认情况下,value1替换value2;

说明 :

  • 如果索引i处无元素,(key1, value1)被添加到该索引处.
  • 如果索引i处只有一个元素,则(key1, value1)与(key2, value2)构成单向链表.如果索引i处有一条单向链表,则按照头插法插入到该链表中.

(3). 随着不断插入数据,在满足下列条件的情况下,可以考虑扩容.


(size >=threshoud) && (null !=table[i])


  • threshold为临界值,当添加的元素的个数>threshold(数组的长度*加载因子,加载因子一般被初始化为0.75f)时.考虑扩容.
  • 默认的临界值 : 16*0.75=12.默认扩容为原来的两倍.始终保证数组的长度是2的整数倍.

2.JDK8与JDK7的不同之处

(因为我下载的是JDK17版本,听说17与8版本差别不大,所以在此查看的是17版本的源码.)

(1).

HashMap<String, Integer> map =new HashMap<>();

//底层并不会初始化数组.只是做了一个初始化操作.

public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
loadFactor是加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
DEFAULT_LOAD_FACTOR全局常量为0.75

当首次put(key1, value1)时,才会初始化数组.

put方法底层会调用到putVal方法.

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

首先key被扔到hash()方法中,我们查看hash方法的源码.

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

先调用key的hashCode()方法计算得到的哈希值1,再进行计算,得到哈希值2.并将哈希值2返回作为putVal方法的实参传入.

我们再看putVal方法的源码.

table是Node类型的数组.Node实现了Map.Entry接口.类似于JDK7中的Entry类.

transient Node<K,V>[] table;
 
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
 
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

第一个if判断table是否分配了的内存空间,第一次put(key1, value1),table =null.所以table调用了resize()方法,初始化table数组.

我们再查看resize()源码.

oldTable指向了table.而且oldCap等于0.所以并没有进入到前面的if(oldCap >0)等语句中.直接看else语句.

else {        // zero initial threshold signifies using defaults
      newCap = DEFAULT_INITIAL_CAPACITY;
      newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

可以清晰的看到 :

newCap为初始化时数组的长度.即16.

newThr为初始化时数组的临界值,即12.

截取了部分代码.可以看到,底层new了一个长度为16的Node类型的数组.而且将数组返回.

按住Ctrl+Alt+←

再查看putVal源码.

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

第二个if语句中,(n-1)&hash求出索引i.如果tab[i]为null意味着此处无元素,那么将新new的Node对象放到该数组位置上.


至于(n-1)&hash设计的很巧妙.由于n为数组的长度.数组的长度都是2的倍数.如初始化时数组的长度为16.n-1用二进制表示为1111.那么只需对比hash二进制表示的后四位进行位运算.计算得到的索引必然也是在0到15内.这也是为什么每次数组扩容默认的都是2倍的原因.


因为执行了上面的if语句,下面的else语句跳过.执行第三个if语句.

如果++size>临界值,那么将扩容.

相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
27天前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
86 43
Java学习十六—掌握注解:让编程更简单
|
9天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
25 3
|
12天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
14天前
|
运维 自然语言处理 供应链
Java云HIS医院管理系统源码 病案管理、医保业务、门诊、住院、电子病历编辑器
通过门诊的申请,或者直接住院登记,通过”护士工作站“分配患者,完成后,进入医生患者列表,医生对应开具”长期医嘱“和”临时医嘱“,并在电子病历中,记录病情。病人出院时,停止长期医嘱,开具出院医嘱。进入出院审核,审核医嘱与住院通过后,病人结清缴费,完成出院。
45 3
|
19天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
22天前
|
移动开发 前端开发 JavaScript
java家政系统成品源码的关键特点和技术应用
家政系统成品源码是已开发完成的家政服务管理软件,支持用户注册、登录、管理个人资料,家政人员信息管理,服务项目分类,订单与预约管理,支付集成,评价与反馈,地图定位等功能。适用于各种规模的家政服务公司,采用uniapp、SpringBoot、MySQL等技术栈,确保高效管理和优质用户体验。
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
53 5
|
1月前
|
算法 索引
让星星⭐月亮告诉你,HashMap的resize()即扩容方法源码解读(已重新完善,如有不足之处,欢迎指正~)
`HashMap`的`resize()`方法主要用于数组扩容,包括初始化或加倍数组容量。该方法首先计算新的数组容量和扩容阈值,然后创建新数组。接着,旧数组中的数据根据`(e.hash & oldCap)`是否等于0被重新分配到新数组中,分为低位区和高位区两个链表,确保数据迁移时的正确性和高效性。
64 3
|
1月前
|
存储 算法 Java
带你学习java的数组军队列
带你学习java的数组军队列
35 0