Java 中HashMap 详解

Java HashMap详解及实现原理 Java HashMap是Java集合框架中最常用的实现Map接口的数据结构,它使用哈希表实现,允许null作为键和值,可以存储不同类型的键值对。HashMap提供了高效的存取方法,并且是非线程安全的。在Java中,HashMap被广泛应用于各种场景,如缓存、数据库连接池、路由器等。 阅读详情
AI摘要

本文详述了Java中HashMap的存储结构,包括数组+链表+红黑树的组合,以及put和get操作的过程。在put操作中,HashMap会根据key的hash进行定位,并在必要时进行扩容或转换为红黑树。get操作则通过hash快速定位到键值对。文中还讨论了HashMap的扩容策略,以及transient关键字在序列化中的作用。

本篇重点:

1.HashMap的存储结构

2.HashMap的put和get操作过程

3.HashMap的扩容

4.关于transient关键字

HashMap的存储结构

1. HashMap 总体是数组+链表的存储结构, 从JDK1.8开始,当数组的长度大于64,且链表的长度大于8的时候,会把链表转为红黑树。

2. 数组的默认长度是16。数组中的每一个元素为一个node,也就是链表的一个节点,node的数据包含: key的hashcode, key, value,指向下一个node节点的指针。

部分源码如下:

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;
        }
...
}

3. 随着put操作的进行,如果数组的长度超过64,且链表的长度大于8的时候, 则将链表转为红黑树,红黑树节点的结构如下,TreeNode继承的LinkedHashMap.Entry是继承HashMap.Node的,所以TreeNode是上面Node的子类。

static final 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;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
//...
}

4. HashMap类的主要成员变量:

/* ---------------- Fields -------------- */

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

View Code

HashMap的put操作过程

本小节讲述put操作中的主要步骤,细小环节会忽略。

1. map.put(key, value),首先计算key的hash,得到一个int值。

2.如果Node数组为空则初始化Node数组。这里注意,Node数组的长度length始终应该是2的n次方,比如默认的16, 还有32,64等

3.用 hash&(length-1) 运算得到数组下标,这里要提一句,其实正常我们最容易想到的,而且也是我之前很长一段时间以为的,这一步应该进行的是求模运算: hash % length ,这样得到的正好是0~length-1之间的值,可以作为数组的下标, 那么为何此处是位与运算呢?

先说结论: 上面提到数组的长度length始终是2^n,在这个前提下,hash & (length-1) 与hash % length是等价的。 而位与运算更快。这里后面会另开一遍进行详解。

4.  如果Node[hash&(length-1)]处为空,用传入的的key, value创建Node对象,直接放入该下标;如果该下标处不为空,且对象为TreeNode类型,证明此下标处的元素们是按照红黑树的结构存储的,将传入的key,value作为新的红黑树的节点插入到红黑树;否则,此处为链表,用next找到链表的末尾,将新的元素插入。如果在遍历链表的过程中发现链表的长度超过了8,此时如果数组长度<64则进行扩容,否则转红黑树。

5. 如果key的hash和key本身都相等则将该key对应的value更新为新的value

6. 需要扩容的话则进行扩容。

注意:

1. 如果key是null则返回的hash为0,也就是key为null的元素一直被放在数组下标为0的位置。

2. 在JDK 1.8以前,链表是采用的头部插入的方式,从1.8改成了在链表尾部插入新元素的方式。 这么做是为了防止在扩容的时候,多线程时出现循环链表死循环。具体会新开一遍进行详细演绎。

HashMap的get操作过程

get的过程比较简单。

1. map.get(key). 首先计算key的hash。

2. 根据hash&(length-1)定位到Node数组中的一个下标。如果该下标的元素(也就是链表/红黑树的第一个元素)中 key的hash的key本身 都和传入的key相同,则证明找到了元素,直接返回即可。

3.如果第一个元素不是要找的,如果第一个元素的类型是TreeNode,则按照红黑树的查找方法查找元素,如果不是则证明是链表,按照next指针找下去,直到找到或者到达队尾。

HashMap的扩容

先说这里的两个概念: size, length.

size:是map.size() 方法返回的值,表示的是map中有多少个key-value键值对儿

length: 这里是指Node数组的长度,比如默认长度是16.

如下面的代码:

        Map<Integer,String> map = new HashMap<>();
        map.put(1,"a");
        map.put(2,"b");
        map.put(3,"c");    

没有在构造函数中指定HashMap的大小,则数组的长度length取默认的16,put了3个元素,则size为3.

Q: 何时需要扩容呢?

A: 在put方法中,每次完成了put操作,都判断一下++size是否大于threshold,如果大于则进行扩容: 调用resize()方法。

Q: 那么threshold又是如何得到的呢?

A: 简单来讲threshold = length * loadfactor(默认为0.75)。 也就是说默认情况下,map中的键值对的个数(size)大于Node数组长度(length)的75%时,就需要扩容了。

Q: 扩容时具体做什么呢?

A: 首先计算出新的数组长度和新的threshold(阈值). 简单来讲,新的length/capacity 是原来的2倍(位运算左移一位),新的threshold为原来的2倍。 还有一些细节此处不再赘述。创建新的Node数组,将原来数组中的元素重新映射到新的数组中。

关于transient关键字

transient关键字的作用:用transient关键字修饰的字段不会被序列化

查看下面的例子:

public class TransientExample implements Serializable{
    private String firstName;
    private transient String middleName;
    private String lastName;

    public TransientExample(String firstName,String middleName,String lastName) {
        this.firstName = firstName;
        this.middleName = middleName;
        this.lastName = lastName;
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("firstName:").append(firstName).append("\n")
                .append("middleName:").append(middleName).append("\n")
                .append("lastName:").append(lastName);
        return sb.toString();


    }


    public static void main(String[] args) throws Exception {
        TransientExample e = new TransientExample("Adeline","test","Pan");

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/path/testObj"));
        oos.writeObject(e);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/path/testObj"));
        TransientExample e1 = (TransientExample) ois.readObject();

        System.out.println("e:"+e.toString());
        System.out.println("e1:"+e1.toString());


    }
}

View Code

输出结果:

e:firstName:Adeline
middleName:test
lastName:Pan

e1:firstName:Adeline
middleName:null
lastName:Pan

被transient关键字修饰的middleName字段没有被序列化,反序列化回来的值是null

Q:HashMap类是实现了Serializable接口的,那么为何其中的table, entrySet变量都标为transient呢?

A:我们知道,table数组中元素分布的下标位置是根据元素中key的hash进行散列运算得到的,而hash运算是native的,不同平台得到的结果可能是不相同的。举一个简单的例子,假设我们在目前的平台有键值对 key1-value1,计算出key1的hash为1, 计算后存在table数组中下标为1的地方,假设table被序列化了,并传输到了另外的平台,并反序列化为了原来的HashMap,key1-value1仍然存在下标1的位置,当在这个平台运行get("key1")的时候,可能计算出key1的hash为2,就有可能到下标为2的地方去找该元素,这样就出错了。

Q:那么HashMap是如何实现的序列化呢?

A:HashMap是通过实现如下方法直接将元素数量(size), key, value等写入到了ObjectOutputStream中,实现的定制化的序列化和反序列化。在Serializable接口中有关于这种做法的说明。

private void writeObject(java.io.ObjectOutputStream out)

throws IOException

private void readObject(java.io.ObjectInputStream in)

throws IOException, ClassNotFoundException;

 

java数据结构之HashMap HashMap 数据结构示意图最近闲来无事,准备将HashMap插入数据的过程,以及其数据结构的转化过程,再回顾一下,故写此篇文章,以是记录。 阅读详情

相关推荐

java 数据结构-HashMap

(1)HashMap 是基于 Map 接口的非同步实现,线程不安全,是为了快速存取而设计的;它采用 key-value 键值对的形式存放元素(并封装成 Node 对象),允许使用 null 键和 null 值,但只允许存在一个键为 null,并且存放在 Node[0] 的位置,不过允许存在多个 value 为 null 的情况。

决战软件之巅的博客 1361

java中hashmap的实现原理与底层数据结构

Hashmap是java面试中经常遇到的面试题,大部分都会问其底层原理与实现,本人也是被这道题问惨了,为了能够温故而知新,特地写了这个博客,以便时时学习。 Hash结构 HashMap根据名称可知,其实现方法与Hash表有密切关系。在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能。 数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给...

技术小白的博客 8万+

从零搭建自动驾驶仿真环境:我的Nuplan-devkit实战笔记(含PyCharm配置技巧)

本文详细记录了从零搭建Nuplan-devkit自动驾驶仿真环境的完整流程,包括conda环境配置、pip版本控制、PyCharm专业版集成等关键步骤。特别针对Ubuntu系统下的依赖管理、数据目录优化和性能调优提供了实用技巧,帮助开发者高效构建稳定的自动驾驶仿真开发环境。

weixin_30505225的博客 479

Java 集合系列之 HashMap详细介绍(源码解析)和使用示例

概要 这一章,我们对HashMap进行学习。 我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap。内容包括: 第1部分 HashMap介绍 第2部分 HashMap数据结构 第3部分 HashMap源码解析(基于JDK1.6.0_45)     第3.1部分 HashMap的“拉链法”相关内容     第3.2部分 HashMa

fivehundredyearsago的博客 938

STC8H硬件IIC从机模式实战:手把手教你驱动OLED屏幕(附完整代码)

本文详细介绍了如何使用STC8H单片机的硬件IIC从机模式驱动SSD1306 OLED屏幕,包括硬件连接、寄存器配置、中断处理和实际应用中的优化技巧。通过完整的代码示例和实战经验分享,帮助开发者快速掌握I2C通信协议和OLED屏幕驱动技术,提升嵌入式开发效率。

weixin_33747129的博客 336

基于DFT的滤波系列:加窗滤波的 Matlab 实现

在实际应用中,你可以尝试不同类型的窗函数(如汉宁窗、汉明窗等)以及不同的滤波参数来获得所需的滤波效果。此外,还可以进一步了解 DFT 和窗函数的原理,以及其他数字信号处理技术的应用。加窗滤波是一种常用的数字信号处理技术,它通过在频域上应用窗函数来改善信号的频谱特性。我们通过定义输入信号、应用窗函数、进行 DFT 和逆 DFT,实现了滤波过程,并通过绘图进行了可视化。在频域上进行加窗滤波的关键是将窗函数应用于 DFT 的结果。首先,我们需要定义一个输入信号,这里我们使用一个简单的正弦波作为示例。

m0_47037246的博客 462

java中HashMap详解

 本文转自:http://alex09.javaeye.com/blog/539545java中HashMap详解HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。 实际上,HashSet 和

caihaijiang的专栏 14万+

几种常见的Web攻击

几种常见的Web攻击 文章目录几种常见的Web攻击一、DoS攻击1.SYN洪水攻击2.IP欺骗3.Land攻击4.针对DoS攻击的防御二、CSRF攻击1. CSRF攻击的发生有三个必要条件:2.几种防护方法:三、XSS漏洞攻击1.非持久型XSS漏洞2.持久型XSS漏洞四、SQL注入1.SQL注入的危害2.SQL注入的方式3.防止SQL注入的解决方案 一、DoS攻击 DoS(Denial of Service),拒绝服务,顾名思义这种攻击是为了让服务器无法提供正常服务,最常见的DoS攻击是网络带宽攻击和连

wolfGuiDao的博客 1259

HashMap详解 Java [ 基础 ] ✅

HashMap是Java中一个强大且高效的集合类,用于快速查找和存储键值对。理解其工作原理和常用操作对于提高编程效率和解决复杂问题非常有帮助。

lenyan~的博客 2万+

2026从0到1!C#工控机开发实战指南:新手一周就能跑通的完整项目

这篇文章摘要如下: 工业自动化领域工控机开发入门指南,针对零基础开发者提供从环境搭建到项目实战的全流程指导。文章首先解答工控机开发的3个核心问题,比较工控机与普通PC的区别,推荐使用C#作为开发语言。随后详细讲解开发环境配置、四大核心技术栈(通信协议、数据库、UI框架、多线程),并重点演示Modbus TCP通信的实现。最后通过一个完整的温度监控系统项目实战,展示如何用WPF构建实时数据采集界面,包括Modbus通信类编写、数据采集服务实现等关键代码片段。全文采用图解+代码的方式,帮助开发者快速掌握工控系统

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

【Simulink教程案例18】基于simulink的基础OFDM+64QAM发射接收链路仿真

OFDM(Orthogonal Frequency Division Multiplexing)即正交频分复用技术,实际上OFDM是MCM(Multi Carrier Modulation),多载波调制的一种。通过频分复用实现高速串行数据的并行传输, 它具有较好的抗多径衰落的能力,能够支持多用户接入。OFDM技术由MCM(Multi-Carrier Modulation,多载波调制)发展而来。OFDM技术是多载波传输方案的实现方式之一,它的。

FPGA/MATLAB学习教程/源码/项目合作开发 1984

Java HashMap 详解

Java HashMap 详解

T_Y_F_的博客 2260

三连杆平面机器人的动力学

0.前言 在这个例子中推导并实现三连杆平面机器人的运动方程。特别的,这里使用拉格朗日法推导运动方程。系统模型如下: 其中在枢轴点处Tm为驱动力矩【一般由伺服电机提供】; b*theta_d:为粘性阻尼扭矩 1、系统方程符号推导前--简介 首先明确欧拉-拉格朗日运动方程: 同样也可以根据作用在多体系统上的非保守力和非保守扭矩来定义广义力Qk。 作用在系统上的广义力的公式为: 其中: Qk:是与广义坐标关联的广义力 Nf_nc :是作用的非保守力的数量 Nt_nc:是作用的非保守扭矩的.

weixin_39090239的博客 4930

Java中HashMap详解

HashMap的扩容

攻城狮Chova的博客 5773

【STM32+K210项目】基于K210智能人脸识别+车牌识别系统(完整工程资料源码)

随着时代的进步和科技的迅猛发展,人们对居住安全的重视程度不断提升,对安全防范系统的需求也日益增强。在安防系统中,门禁系统占据着重要的位置。传统的门禁控制方式主要依靠钥匙、密码和门禁卡等,这些方式易于被破解和复制,安全性较低。然而,近年来,生物识别技术的迅速发展和成熟,为解决这一问题提供了新的方案。生物识别技术具备便携性、非易失性、不易遗忘以及难以盗用等优点,逐渐被引入到门禁系统中。传统的小区门禁系统往往依赖人工管理,存在一定的局限性,难以满足用户多样化的需求,也浪费了人力资源。

嵌入式技术开发 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

【JAVA】 HashMap详解

HashMap详解

weixin_37559642的博客 1289

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

JAVA之HashMap详解

HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在,即主要用来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。它的 key、value 都可以为 null,此外,HashMap 中的映射不是有序的。jdk1.8 之前 HashMap 由数组 + 链表组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突(两个对象调用的 hashCode 方法计算的哈希值经哈希函数算出来的地址被别的元素占用)而存在的(“拉链法”解决冲突)。

励志成为一名优秀的全能型软件开发工程师。 2328

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

jre9下载 jdk9

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

HashMap与Hashtable的区别及Java集合详解

本文详细对比了HashMap和Hashtable的区别,包括线程安全性、null值支持、性能、扩容机制等,并系统介绍了Java集合框架。HashMap非线程安全但性能高,Hashtable线程安全但效率低,推荐使用ConcurrentHashMap替代。文章还分析了List、Set、Map接口的主要实现类特点及适用场景,提供了集合选择指南和使用注意事项,包括正确实现hashCode/equals、线程安全选择等。最后介绍了JDK新增的of()方法和不可变集合特性,帮助开发者根据实际需求选择最合适的集合实现。

weixin_47242663的博客 748

Java中HashMap的用法详解

package com.mbyte.easy.admin.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @Author: wqy * @Date: 2019-06-24 16:52 * @Versio...

qq_34746239的博客 4297

Java中的HashMap详解

一、什么是HashMap HashMap继承了AbstractMap,实现了Map接口,存储的是一个键值对对象。 二、HashMap数据结构解析 1、继承关系:HashMap继承了AbstractMap,实现了Map接口。 public abstract class AbstractMap<K,V> implements Map<K,V> { 2、常量及构造方法 //...

总结经验,与君共勉 3297

java的hashmap详解

背景 hashmap作为java使用最多的容器,不管是实际工作中还是面试中都是经常看到的,这里详细说明下HashMap的设计 术语 查看代码的过程会遇到很多术语以及单词,这里先梳理下 table 指的是hashtable,里面存放着桶 burket 桶,也就是数组的元素,里面内容是对应key的hash值 capacity 桶的大小,也就是数组的长度 bin 桶对应的链表或者数中的节点,就是最终的...

得救之道就在其中 527

【Java基础】HashMap详解

全方位详解HashMap

scj1022的博客 1827
上一篇: 一个注解解决ShardingJdbc不支持复杂SQL
下一篇: Java 流处理之收集器
java小姜在线冲
博客等级 码龄4年 53粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值