Hash及HashMap底层的一些理解

1 篇文章 0 订阅
1 篇文章 0 订阅

一、铺垫

本文不说那些高端名词,我打算用一些问题引出一些接地气的东西

问题一:

一般我们对hash的理解,是不同对象的hash不同,甚至是同一个对象的不同命实例的hash不同

那么问题是:“如果在一个循环中,对某个对象创建了多个同名的实例,那么它们的hash值会否相同”

要回答这个问题,直接上个图大家就了解了

javadoc上说的很明白了,对Object的获取hash值操作,不依赖于java语言。只与实例所在内存的地址有关

这样就好理解了,为什么同一个实例,它的hash值永远一致。

 

问题二:

都在说hash、hash的,那么hash是什么?有人说,hash就是散列。

问题是:“散列又特么的是什么呢?”

我的理解,散列就是把那些实例们分散开排队

比如,超市会设置多个收银员为我们结账。那我们在超市付款,大家一定会找那些人少的队伍去排队。

所以,这种把人分散到不同队伍的操作,就叫散列。

这样一来,由串行改并行,效率提高了有木有。所以大家心情美美哒。

 

问题三:

有人感觉Hash容器(比如HashMap),特别是jdk8之后的HashMap的底层实现机制为什么似曾相识呢?

是的,我感觉HashMap的实现机制,遵循的就是桶排序算法的思路。

本来桶排序算法是一个限制多多,诟病多多的算法。

比如,桶排序的待排元素只能是整形;再比如,待排序的数据间跨度不能太大。跨度太大会有大量的空置的桶,造成空间浪费。

但是,java的Object类自带的hashCode()方法为每个实例计算出的hash值的数位,桶排序算法就彻底释放出了它的活力了。

那怎么控制hash桶不会有浪费呢?

HashMap中有一个专门的table,用来记录当前HashMap的hash桶中都记录了哪些键。

这样,就不需要为了适配所有进入HashMap的hash值的可能性,而造成的hash桶浪费了

其作用,是在对HashMap做put和get操作时候,需要先去table中校验一下当前的key是否存在的。

 

问题四:

学习hash、HashMap的思想有啥用?

我觉得吧,在分布式架构流行的今天,了解一些map的hash机制、resize扩容机制

对设计分布式系统、考虑系统的水平扩容、负载均衡、session保存什么的都有很好的借鉴意义

比如jdk1.8之后的HashMap的实现方式,是hash桶与链表并存的机制。

同时,hash桶内部的链表还会在相同hash值较多的情况下,由链表进化为红黑树的可能

同时,桶内部的二次查找算法,也由比较算法变为了对树的二分查找

 

二、正题

铺垫完毕,说说我的一些理解吧。

 

2.1:HashMap的扩容机制与reHash

总听说“reHash”这一叫法,其实,如果真的了解桶排序算法的思想,那么我觉得这个叫法是不准确的。

扩容就是reSize,不是reHash

因为reHash这个叫法会让大家感觉,存入HashMap中的元素的hash值被改变了。其实改变这件事是不存在。

说说我的理解:

2.1.1:hashCode与容器无关

java在向hash容器(比如HashMap、HashTable)中添加对象的时候,首先要获取对象的hashCode。

而hashcode不依赖于hash容器。上面也说了,hashCode这东西基本与对象在内存中的地址什么的有关。

所以,只要内存地址不变,对象的hashCode不变。

所以,HashMap再怎么扩容,与它里面的那些对象们有个毛蛋的关系啊。

2.1.2:hash容器的扩容机制

已经有好多人聊过HashMap的resize这段代码都干了什么了,在此不再详述,感兴趣的诸位可以从这里进去看看

我对hash容器(以HashMap为例)扩容这件事的理解是这样的

第一,HashMap的初始大小可能是16,扩容因子是0.75。

那么初始状态下,它里面只能存放12个不同的hashCode,如果我们继续向HashMap里面添加元素时

进来的可能会是一个新的hashCode,受扩容因子限制,那么hashMap就要扩容了

所以它扩容的唯一原因,就是因为它里面放不下更多的、不同的hashCode了。就这么简单。

还有的朋友说,当你需要重写某个类的equal方法的时候,需要在一定程度上重写hashCode函数

感兴趣的诸位可以从这里进去看看 。

 

### 回答1: 我没有查看Java的HashMap底层代码,但是我可以给你提供一些关于它如何工作的信息。HashMap存储键/值对,其中键和值可以是任何类型的Java对象。它使用哈希函数来根据键对对象进行分组,并使用二叉搜索树来确定特定键的值。 ### 回答2: 哈希表(hash map)是Java中常用的数据结构之一,用于存储和检索键值对数据。底层代码实现了将键值对映射到哈希表中的具体操作。 在Java中,HashMap类是实现哈希表的典型示例。底层HashMap代码主要涉及以下几个方面: 1. 数据结构:HashMap底层使用数组和链表/红黑树结构实现哈希表。数据存储在一个数组中,每个数组元素又是一个链表/红黑树,用于处理发生哈希冲突时的情况。 2. 哈希函数:HashMap使用哈希函数将键转换为哈希码(hash code),以确定键值对的存储位置。Java中的哈希函数是通过对键的对象调用hashCode()方法实现的。 3. 存储与检索:当需要存储一个键值对时,HashMap通过计算键的哈希码,然后将哈希码映射到具体的数组位置。如果发生哈希冲突,即多个键的哈希码映射到同一个位置,HashMap使用链表或红黑树解决冲突。在进行键值对检索时,根据键的哈希码找到对应的位置,并遍历链表/红黑树查找指定键。 4. 动态调整大小:HashMap会根据当前数据量的大小动态调整数组的大小(扩容/缩容)。这是因为过多的哈希冲突会降低HashMap的性能,同时合理的负载因子(load factor)可以确保哈希表的平衡。 总之,HashMap底层的实现涉及数据结构、哈希函数、哈希冲突解决、存储和检索等重要部分。通过查看源代码,可以更好地理解HashMap的内部工作原理,以及其在存储和检索键值对数据时所采取的具体措施。 ### 回答3: 要查看Java的HashMap底层代码,您可以通过以下步骤进行: 1. 打开Java的HashMap类:打开您的Java开发工具或集成开发环境(IDE),并打开java.util包中的HashMap类。这是HashMap的标准实现。 2. 查看HashMap的构造方法:在HashMap类中,搜索构造方法部分。您可以看到HashMap类有多个构造方法,用于创建不同类型的HashMap实例。 3. 理解HashMap的实现原理HashMap是基于哈希表的数据结构,它使用键-值对的存储方式。在HashMap类中,找到实例变量部分,这些变量定义了HashMap内部用于存储键值对的数据结构和相关参数。 4. 查看put()方法的实现:在HashMap类中,找到put()方法的定义。这是向HashMap中插入键值对的方法。通过分析该方法的代码,您可以了解到HashMap是如何处理碰撞、计算哈希值、处理哈希冲突以及更新内部数据结构的。 5. 查看get()方法的实现:在HashMap类中,找到get()方法的定义。该方法允许您根据给定的键获取对应的值。通过分析该方法的代码,您可以了解HashMap是如何根据键计算哈希值并按照哈希值找到对应的桶(存储链表或红黑树),然后在桶中搜索并返回对应的值的。 6. 阅读其他方法的实现:除了put()和get()方法,HashMap还提供了其他一些常用的方法,如remove()、containsKey()、containsValue()等。通过查看这些方法的实现,您可以进一步了解HashMap底层实现原理和数据结构。 虽然直接从Java的源代码中阅读HashMap底层实现会非常具有挑战性,但通过仔细阅读并分析代码,您可以获得对HashMap底层工作原理和数据结构的深入理解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值