Java中HashMap的实现原理

article/2025/11/9 10:23:50

一、Java中的hashCode和equals

1、关于hashCode

  1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的
  2. 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同
  3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点
  4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里“

再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。

以下对hashCode的解读摘自其他博客:

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0  1  2  3  4  5  6  7 
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

2、关于equals

1.equals和==
==用于比较引用和比较基本数据类型时具有不同的功能:
比较基本数据类型,如果两个值相同,则结果为true
而在比较引用时,如果引用指向内存中的同一对象,结果为true;

equals()作为方法,实现对象的比较。由于==运算符不允许我们进行覆盖,也就是说它限制了我们的表达。因此我们复写equals()方法,达到比较对象内容是否相同的目的。而这些通过==运算符是做不到的。

2.object类的equals()方法的比较规则为:如果两个对象的类型一致,并且内容一致,则返回true,这些类有:
java.io.file,java.util.Date,java.lang.string,包装类(Integer,Double等)
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
运行结果为false true

二、HashMap的实现原理

1.    HashMap概述

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

    在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

其中Java源码如下:

/*** The table, resized as necessary. Length MUST Always be a power of two.*/
transient Entry[] table;static class Entry<K,V> implements Map.Entry<K,V> {final K key;V value;Entry<K,V> next;final int hash;……
}
 

可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

2、HashMap实现存储和读取

1)存储

1 public V put(K key, V value) {2     // HashMap允许存放null键和null值。3     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。4     if (key == null)5         return putForNullKey(value);6     // 根据key的keyCode重新计算hash值。7     int hash = hash(key.hashCode());8     // 搜索指定hash值在对应table中的索引。9     int i = indexFor(hash, table.length);
10     // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
11     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
12         Object k;
13         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
14             // 如果发现已有该键值,则存储新的值,并返回原始值
15             V oldValue = e.value;
16             e.value = value;
17             e.recordAccess(this);
18             return oldValue;
19         }
20     }
21     // 如果i索引处的Entry为null,表明此处还没有Entry。
22     modCount++;
23     // 将key、value添加到i索引处。
24     addEntry(hash, key, value, i);
25     return null;
26 }
  

根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。

1 static int hash(int h) {
2     h ^= (h >>> 20) ^ (h >>> 12);
3     return h ^ (h >>> 7) ^ (h >>> 4);
4 }

我们可以看到在HashMap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的效率。

根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

通过这种方式就可以高效的解决HashMap的冲突问题。

2)读取

1 public V get(Object key) {2     if (key == null)3         return getForNullKey();4     int hash = hash(key.hashCode());5     for (Entry<K,V> e = table[indexFor(hash, table.length)];6         e != null;7         e = e.next) {8         Object k;9         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
10             return e.value;
11     }
12     return null;
13 }
  

从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

3)归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

3、HashMap的resize

       当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

       那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

 

总结:HashMap的实现原理:

  1. 利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
  4. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

http://chatgpt.dhexx.cn/article/NTFV1Mot.shtml

相关文章

HashMap底层实现原理及扩容机制

HashMap的数据结构&#xff1a;数组链表红黑树&#xff1b;Java7中的HashMap只由数组链表构成&#xff1b;Java8引入了红黑树&#xff0c;提高了HashMap的性能&#xff1b;借鉴一张图来说明&#xff0c;原文&#xff1a;https://www.jianshu.com/p/8324a34577a0 下面简单说一下…

HashMap 实现原理

简介 本文为我对 HashMap 实现原理的笔记整理以及一些个人理解&#xff0c;如若发现有错误的地方&#xff0c;欢迎留言指正 在不同的 Java 版本中 HashMap 的实现也略有不同&#xff0c;本文示例使用的 Java 版本为&#xff1a;“1.8.0_181” 什么是 Hash&#xff08;散列函…

HashMap底层实现原理(看这一篇就够了)

HashMap概述 HashMap实现了Map接口&#xff0c;我们常用HashMap进行put和get操作读存键值对数据。下面介绍基于jdk1.8深入了解HashMap底层原理。 HashMap数据结构 HashMap实际是一种“数组链表”数据结构。在put操作中&#xff0c;通过内部定义算法寻止找到数组下标&#xf…

HashMap的实现原理及其特点

1) HashMap可以接受null键值和值&#xff0c;而HashTable则不能&#xff0c;HashMap是非synchronized的&#xff1b;存储的是键值对。 2) HashMap是基于hashing原理,使用put(key,value)存储对象到HashMap中&#xff0c;使用get(key)从HashMap中获取对象&#xff0c;当我们给pu…

说一下HashMap的实现原理?

点击上方蓝色“趣学程序”&#xff0c;选择“设为星标” 回复“资源”获取独家整理的学习资料&#xff01; 回复“加群”与更多小伙伴共同成长&#xff01; 回复“源码”获取专属项目源码&#xff01; 本文会对java集合框架中的对应实现HashMap的实现原理进行讲解&#xff0c;然…

HashMap的实现原理和底层数据结构

看了下JAVA里面有HashMap、Hashtable、HashSet三种hash集合的实现源码&#xff0c;这里总结下&#xff0c;理解错误的地方还望指正 HashMap和Hashtable的区别 HashSet和HashMap、Hashtable的区别 HashMap和Hashtable的实现原理 HashMap的简化实现MyHashMap HashMap和Hashtable的…

HashMap底层实现原理

HashMap实现原理 1.概述 HashMap是基于哈希表的Map接口的非同步实现。元素以键值对的形式存放&#xff0c;并且允许null键和null值&#xff0c;因为key值唯一&#xff08;不能重复&#xff09;&#xff0c;因此&#xff0c;null键只有一个。另外&#xff0c;hashmap不保证元素…

hashMap实现原理

1. HashMap概述&#xff1a; HashMap是基于哈希表的Map接口的非同步实现&#xff08;Hashtable跟HashMap很像&#xff0c;唯一的区别是Hashtalbe中的方法是线程安全的&#xff0c;也就是同步的&#xff09;。此实现提供所有可选的映射操作&#xff0c;并允许使用null值和null键…

HashMap的实现原理和底层结构

哈希表&#xff08;hash table&#xff09;也叫散列表&#xff0c;是一种非常重要的数据结构&#xff0c;应用场景及其丰富&#xff0c;许多缓存技术&#xff08;比如memcached&#xff09;的核心其实就是在内存中维护一张大的哈希表&#xff0c;而HashMap的实现原理也常常出现…

【java】HashMap底层实现原理

目录 一.哈希表(散列)1.什么是哈希表2.什么是哈希冲突(面试题)3.解决哈希冲突的方法(面试题)(1) 开放地址法① 线性探查②二次探查③随机探查 (2) 再哈希法(3) 链地址法(4)建立公共溢出区 二.HashMap1.HashMap的hash()算法(面试)(1)为什么不是h key.hashCode()直接返回&#x…

HashMap底层实现原理解析

一、HashMap底层实现原理解析 我们常见的有数据结构有三种结构&#xff1a; 数组结构链表结构哈希表结构 下面我们来看看各自的数据结构的特点&#xff1a; 1&#xff09;数组结构&#xff1a; 存储区间连续、内存占用严重、空间复杂度大 优点&#xff1a;随机读取和修改效…

HashMap底层实现原理概述

1. 前言 在一场面试中最能打动面试官的其实是细节&#xff0c;候选人对细节的了解程度决定了留给面试官的印象到底是“基础扎实”还是“基础薄弱”&#xff0c;如果候选人能够举一反三主动阐述自己对一些技术细节的理解和总结&#xff0c;那无疑是面试过程中的一大亮点。HashM…

HashMap实现原理分析

之前转载过一篇HashMap相关分析文章&#xff0c;快速链接&#xff1a;HashMap实现原理分析 既然有前辈已经将源码分析总结了出来&#xff0c;我们在继续学习研究源码实现的时候不妨借鉴借鉴前人的总结与经验~ 本文转自&#xff1a;https://blog.csdn.net/hefenglian/article/…

HashMap面试,看这一篇就够了

历史热门文章&#xff1a; 七种方式教你在SpringBoot初始化时搞点事情 Java序列化的这三个坑千万要小心可以和面试官聊半个小时的volatile原理Java中七个潜在的内存泄露风险&#xff0c;你知道几个&#xff1f;JDK 16新特性一览啥&#xff1f;用了并行流还更慢了InnoDB自增原理…

java中HashMap原理

1、为什么用HashMap&#xff1f; HashMap是一个散列桶&#xff08;数组和链表&#xff09;&#xff0c;它存储的内容是键值对(key-value)映射HashMap采用了数组和链表的数据结构&#xff0c;能在查询和修改方便继承了数组的线性查找和链表的寻址修改HashMap是非synchronized&a…

HashMap的实现原理

1. HashMap概述 HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作&#xff0c;并允许使用null值和null键。此类不保证映射的顺序&#xff0c;特别是它不保证该顺序恒久不变。 在java编程语言中&#xff0c;最基本的结构就是两种&#xff0c;一个…

软件测试用例分析和用例设计

测试用例的概念 测试用例&#xff08;test case&#xff09;&#xff0c;也叫测试案例&#xff0c;是为了达到一个最佳的测试效果或者高效的发现软件中的隐藏错误&#xff08;缺陷&#xff09;而精心设计的包括场景步骤和数据。 通用的定义&#xff1a;是关于一个功能验证时候…

软件测试用例设计练习

1、完成163邮箱注册用例的编写 邮箱地址&#xff1a;6~18个字符&#xff0c;可使用字母、数字、下划线&#xff0c;需要以字母开头 密码&#xff1a;8~16个字符&#xff0c;大、小写字母、数字、标点符号&#xff0c;3种或以上组合 2、需求&#xff1a;输入三条边&#xff…

软件测试用例详细规范

软件测试用例详细规范 为什么编写测试用例详细测试用例模板测试用例字段介绍用例操作步骤用例预期结果&#xff1a; 测试用例录入原则&#xff1a;测试用例设计步骤测试用例案例&#xff1a;测试用例校验点&#xff1a; 为什么编写测试用例 我也不知道&#xff0c;自己百度 详…

软件测试——测试用例设计方法

1、测试用例定义 测试用例又叫test case&#xff0c;是为某个特殊目标而编制的一组测试输入&#xff0c;执行条件以及预期结果&#xff0c;以便测试某个程序路径或核实是否满足某个特定需求。 2、测试用例的特性 有效性&#xff1a;测试用例能够被使用&#xff0c;且被不同人…