【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>临界值,那么将扩容.

相关文章
|
7天前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
27 5
|
2月前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
58 0
|
11天前
|
设计模式 Java
结合HashMap与Java 8的Function和Optional消除ifelse判断
`shigen`是一位致力于记录成长、分享认知和留住感动的博客作者。本文通过具体代码示例探讨了如何优化业务代码中的if-else结构。首先展示了一个典型的if-else处理方法,并指出其弊端;然后引入了策略模式和工厂方法等优化方案,最终利用Java 8的Function和Optional特性简化代码。此外,还提到了其他几种消除if-else的方法,如switch-case、枚举行、SpringBoot的IOC等。一起跟随shigen的脚步,让每一天都有所不同!
27 10
结合HashMap与Java 8的Function和Optional消除ifelse判断
|
19天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
174 37
|
5天前
|
传感器 监控 数据可视化
【Java】智慧工地解决方案源码和所需关键技术
智慧工地解决方案是一种新的工程全生命周期管理理念。它通过使用各种传感器、数传终端等物联网手段获取工程施工过程信息,并上传到云平台,以保障数据安全。
25 7
|
14天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
2月前
|
存储 Java 数据处理
如何使用 Java 迭代 HashMap 中的 ArrayList
【8月更文挑战第23天】
43 2
|
2月前
|
安全 Java
【Java集合类面试十五】、说一说HashMap和HashTable的区别
HashMap和Hashtable的主要区别在于Hashtable是线程安全的,不允许null键和值,而HashMap是非线程安全的,允许null键和值。
|
2月前
|
安全 Java
【Java集合类面试十六】、HashMap与ConcurrentHashMap有什么区别?
HashMap是非线程安全的,而ConcurrentHashMap通过减少锁粒度来提高并发性能,检索操作无需锁,从而提供更好的线程安全性和性能。
|
2月前
|
Java
【Java集合类面试十四】、HashMap是如何解决哈希冲突的?
HashMap解决哈希冲突的方法是通过链表和红黑树:当链表长度超过一定阈值时,转换为红黑树以提高性能;当链表长度缩小到另一个阈值时,再转换回链表。
下一篇
无影云桌面