HashMap实现原理分析

HashMap实现原理和源码详细分析 JDK1.8 HashMap实现原理和源码详细分析,学习要点:知道HashMap的数据结构。了解HashMap中的散列算法。知道HashMap中put、remove、get的代码实现。HashMap的哈希冲突是什么?怎么处理的?知道HashMap的扩容机制 阅读详情

前言:        

HashMap的包结构

HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

*注意,此实现不是同步的。

好吧“哈希表”是啥,内部结构是什么样的?

哈希表:

什么是哈希表?

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
散列函数的构造方法: (1)直接定址法 (2)除留余数法 (3)平方取中法 (4)折叠法 (5)数值分析法
构造散列函数的目的是减少冲突,但要完全避免冲突是不可能的,只能尽可能减少冲突。 处理冲突的方法: (1)开放定址法 (2)二次探测法 (3)链地址法(拉链法)

HashMap是怎么实现散列函数和处理冲突的?

public static void main(String[] args) {
		HashMap<String, String> blogMap=new HashMap<>();
		blogMap.put("博文测试", "博文测试Value");
		blogMap.get("博文测试");//此行打断点
	}

观察Variables,如下图


在图中我们看到了,table是一个数组,看看我们数据在数组中是什么样的

数组索引为9,该位置下存储的数据节点为,看到关键字next,该结构这样看应该是一个数组加链表没错了,那么这样映射了hash表中哪种解决冲突的方法了,对比刚刚列出的几种方法,结构一致的只有拉链法了。


拉链法的简单介绍:

拉链法,我们可以理解为“链表的数组”,他是一种数组加链表的结构,其数据结构如图:


拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。
*->具体构造过程(动画演示: 点击打开链接) 草鸡生动形象啊!,简单生动,建议耐心看完


HashMap 拉链法的实现分析

来我们看看,hashMap是如何计算key的hash的,并如何决定将value放置到具体的数组索引中的,打开源码(可配合Java6的中文API文档,文档注释是人工翻译比较准确,笔者开的是Java8的源码)Java Doc  在线地址(http://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/HashMap.html)
HashMap中的m(散列表长度)和a(装载因子)
JDK源码中定义如下默认值

默认加载因子:
/**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认散列表长度:
/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
HashMap中的PUT方法
我们先从HashMap最常用的两个方法(put,get)为入口对代码进行分析
.put方法源码:
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);//我们看到在方法内部对key做了一次hash运算,然后调用putVal方法,我们暂且不关心后两个参数在干嘛,回头解释
    }

由以上源码,我们知道,在将key和value放入到hashMap中以前,先对key做了一次hash运算,hash算法是什么样的呢

static final int hash(Object key) {//先判断key是否为null,如果为null,hash值为0,否则以hashCode方法值为准
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
*以上引出一个知识点,这就是为什么重写equals方法时,为什么强烈建议重写hashCode方法的原因了
1、集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的。
2、由于Object的hashCode返回的是对象的hash值,所以即使equals返回TRUE,集合也可能判定两个对象不等,所以必须重写hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。

接着来,进入到putVal方法内部
    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    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) //这一行的意思是如果table(主存储结构)没有初始话,则初始化一个数组并赋值给tab,初始化代码在resize方法中
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) //根据散列值算出数组索引((n - 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))))//如果该节点的key一致,那么e=p
                e = p;
            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指针为null
                        p.next = newNode(hash, key, value, null);  //为p后面添加一个节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   //如果链表过长时,转换成红黑树。这个值表示当某个箱子中,链表长度大于 8 时,有可能会转化成树。
                            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;
        if (++size > threshold) //map里的元素个数(size)大于一个阈值(threshold)时,map将自动扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
可以发现HashMap通过键的hashCode来快速的存取元素。
当不同的对象hashCode发生碰撞时,HashMap通过单链表来解决,将新元素加入链表表头,通过next指向原有的元素。单链表在Java中的实现就是对象的引用(复合)。

综上一次put的大概逻辑是
1.计算key的hashCode
2.根据hashCode计算bucketIndex(  (DEFAULT_INITIAL_CAPACITY  - 1) & hash)
在bucketIndex中的处理分三种情形
3.1 BucketIndex没有元素时,直接添加节点
3.2 对象相同时会替换原有KEY
3.3 BucketIndex位置有元素时


到这里,我们了解了HashMap工作原理的一部分,那还有另一部分,如,加载因子及resize(),HashMap通常的使用规则,这些会留在下一章说明。





Android开发者必看:F2FS文件系统如何优化你的手机存储性能(附实测数据对比) 本文深入解析了F2FS文件系统如何针对闪存特性优化Android设备存储性能。通过对比ext4,F2FS采用追加写和冷热数据分离策略,将随机写转化为顺序写,显著提升写入速度并减少GC卡顿。实测数据显示,其在随机写入和SQLite事务等场景下性能提升显著,为开发者提供了优化应用IO性能的关键思路。 阅读详情

相关推荐

本体(Ontology)概述

认识本体        本体(Ontology)的概念源自于哲学领域,在哲学中的定义为“对世界上客观事物的系统描述,即存在论”。哲学中的本体关心的是客观现实的抽象本质。而在计算机领域,本体可以在语义层次上描述知识,可以看成描述某个学科领域知识的一个通用概念模型。德国学者Studer在1998年给出了本体的相关定义“本体是共享概念模型的形式化规范说明”。这个定义包含了四层含义:即共享(share)...

baijinswpu的博客 7万+

HashMap里的Node

一。 HashMap类中的Node<K,V>类里有一个Node<K,V> next。那以Node<K,V>.next.next.next这种结构形式存储元素就是所说的链表,而Node<K,V>[ ] tab就是数组,tab所存储元素为每个链表的第一个元素。 每个数组的位置就是一个哈希值,如果两个值哈希值一样,就会占用一个位置,他们就成了一个链表 ...

止境博客 7907

PrimeTime low power-多电压设计流程(2)

get_power_switches`、`get_supply_nets` 和 `get_supply_ports` 命令的操作方式类似,并具有类似的命令选项。PrimeTime 根据 UPF 命令构建电源供应网络,包括 create_power_domain、create_supply_net、create_supply_set、create_supply_port、set_domain_supply_net 和 connect_supply_net。电源集句柄是为电源域隐式创建的一个抽象电源集。

QRBQ 712

HashMap的实现原理及部分源码分析

HashMap的实现原理及部分源码分析 ​  Java8拥有增强的Map集合,Map是用来保存具有映射关系的数据。Map接口提供了大量的实现类,典型的实现有 HashMap和Hashtable等。Hashtable从它的类名看起来就很古老,因为它的命名都没有遵守Java的命名规范:每个单词首字母都应该大写。 HashMap(哈希表又叫散列表),在数据结构我们已经接触过,它的应用非常广泛,...

weixin_42378758的博客 268

C#串口通信从入门到精通(27)——高速通信下解决数据处理慢的问题(20ms以内)

本文介绍如何在高速通信情况下解决串口接收的数据来不及处理的问题,由高级工程师多年经验总结而成,绝对是串口领域的实战经验!

qq_34059233的博客 6807

把 ABAP ALE Change Pointers 讲透:从 CDHDR/CDPOS 到 BDCP2,再到 RBDMIDOC 的自动分发链路

摘要:SAP ALE Change Pointers机制在系统集成中实现业务变更与数据分发的解耦。它记录业务对象变更(如SAP主数据修改),但不立即发送,而是由后续分发逻辑决定发送时机和方式。该机制包含两层:Change Documents记录审计级变更明细,Change Pointers标记需分发的关键变更。配置核心包括全局开关、消息类型激活和字段选择。典型应用如零售价格条件变更自动推送至POS系统,通过BDCP2表存储待处理队列,由RBDMIDOC程序定时评估生成IDoc。运维需关注定期清理和性能优化,

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 209

HashMap实现原理及源码分析

HashMap实现原理及源码分析

u010919133的专栏 212

SAP 错误消息调试之七种武器:让所有的错误消息都能被定位

目录 长生剑 - SAPGUI Where Used List 碧玉刀 - ABAP调试器观察点 霸王枪 - ABAP调试器动态断点 多情环 - ABAP代码静态扫描 孔雀翎 - SAT 离别钩 - ST05 拳头 - 迷之方法??? Jerry在2018年开始用中文在微信公众号平台上写作之前,在SAP官方社区上总共写了639篇英文博客。 其中有一篇写于2013年介绍ABAP调试技巧的博客:

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2809

总结HashMap实现原理分析

一、底层数据结构 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低。 而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8),时,将链表转换为红黑树,这样大大减少了查找时间。 二、HashMap的实现原理: JDK1.7中的Ha...

洞玄之境的博客 2万+

A Macro- and Micro-Hierarchical Transfer Learning Frameworkfor Cross-Domain Fake News Detection

近年来,社交媒体上假新闻的快速和广泛传播对人们的日常生活构成了越来越大的威胁[6, 14, 19]。为了有效打击假新闻,自动检测假新闻变得至关重要[39]。新闻内容[11, 16, 20, 27]和用户互动(即用户互动)[1, 24, 34]。基于这些类型的数据,提出了单领域假新闻检测,它从新闻文章、用户互动或两者中提取有效特征,以识别单个新闻领域内新闻的真实性(即新闻是真是假)(例如,政治新闻或娱乐新闻)。

qq_52053775的博客 155

RT-DETR改进策略【模型轻量化】| 替换骨干网络为 2024轻量化网络MoblieNetV4:移动生态系统的通用模型

本文记录的是基于MobileNet V4的RT-DETR目标检测轻量化改进方法研究。通过整合UIBMobile MQA以及优化的NAS策略能够在在不降低性能指标的前提下,降低计算成本。本文配置了原论文中和五种模型,以满足不同的需求。

Limiiiing的博客 1922

hashMap实现原理分析

哈希表也叫散列表,是一种非常重要的数据结构,许多缓存技术的核心就是在内存中维护一张大的哈希表,应用非常广泛。 hashMap是一个散列表,它存储的内容是键值对映射 hashMap继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口 hashMap不是线程安全的,同时也不是有序的 hashMap构造函数 //无参构造方法,构造一个空的hashm...

仰望星空 194

【Envi遥感图像处理】022:envi时空分析工具的使用

本文介绍了ENVI软件中的时空分析工具应用,以美国拉斯维加斯1975-2014年的9期Landsat数据为例。主要内容包括:1)时空分析概念,即对空间相关时间序列数据的建模分析;2)构建时序数据流程,通过添加栅格、时间排序等步骤生成.series格式时序文件;3)时序数据查看方法,包括动画播放、添加注记及保存视频;4)多源数据统一处理工具,解决坐标系、空间范围不一致问题。该工具可用于冰川变化、植被覆盖等时空变化研究。

智慧空间实验室 270

HashMap实现原理以及性能分析

本篇博文转自:DannyHoo的HashMap实现原理及源码分析 一、背景 在java中,HashMap是很常用的一种数据结构,最近重新温习了一下,这里以源码层面来分析总结一下HashMap,如有不合理或疑问的地方,欢迎沟通交流。 HashMap是Java中的一个容器,继承自AbstractMap抽象类,实现Map接口,简单画了一张Map家族的类图(只画了部分常用的接口和类): 在Map家族中...

iWuxingyun的博客 1219

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 利用tomcat插件实现自动部署
下一篇: HashMap实现原理分析-resize()详解
weiythi
博客等级 码龄16年 23粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值