HashMap的一些小理解

本文针对HashMap的数据结构特性及其实现原理进行了深入探讨,包括其内部结构、查询速度、扩容策略等核心问题,并提供了作者个人的理解与见解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近看了数据结构与算法,并在网上看了关于Hashmap源码解析的博客,有了一些小感悟,这里对于源码就不再解释说明,想要了解的,可以去看连接中的博客,感觉写的还是不错的,虽然有些观点不太认同。

如果大家要去看的话,请带着下面几个问题来看:

1. 为什么需要HashMap这种数据结构?

2. Hashmap内部的实现是使用的什么数据结构(数组还是链表,亦或者数组加链表)?如果是链表的话,又是使用的什么数据结构来构建的?

3. 为什么Hashmap的查询速度会那么快?是怎么做到的?

4. Hashmap为什么会选择那个值来作为扩容的阀值?

5.在扩容的时候,为什么还要再计算一次,而不是简单的拷贝到新的数组里面?

6. 为什么会把null放在0的位置?

7. 为什么以2倍的原长度来进行扩容呢?

8. 为什么会有个modcount,是做什么用的?

9. 为什么在扩容的时候,会有一个最大值的限制?

10. 迭代器怎么实现?(凑个数,灵魂十问)

下面针对上面十个问题,说一下个人的理解:

1. 为什么需要HashMap这种数据结构?

对于第一个问题,是希望大家可以认真思考一下,自己的业务场景使用这种结构是不是最优的,是否可以通过其它的集合方式来替换。这里个人理解的场景是,你的业务场景需要<key,value>,或者需要依靠一个值或对象快速的找到你要的value的情景,才要去使用它,否则不要去使用它,因为它的构造需要的计算量以及复杂度比较高,不如直接使用简单的集合。

2. Hashmap内部的实现是使用的什么数据结构(数组还是链表,亦或者数组加链表)?如果是链表的话,又是使用的什么数据结构来构建的?

对于第二个问题,Hashmap中使用的是数组结合链表的形式,我们需要关注亮点,一点是数组的构建,用的是哪种类型的数组,第二点就是所使用类型的内部结构是什么样的,这里数组类型是Entry的,其内部结构包括key,value,next(用于创建单链表)以及hash(用于扩容的时候进行重计算)。

3. 为什么Hashmap的查询速度会那么快?是怎么做到的?

对于第三个问题,看过源码以后你就会知道,其查找过程,是通过计算直接得到在数组中的位置,而不是遍历的去比较。

4. Hashmap为什么会选择那个值来作为扩容的阀值?

在我们对hashmap进行查找的时候,假如链表很长的话,我们的查找效率会变低,因为在链表中找的过程是逐个遍历的,而不是通过计算直接得到的,而这个链表的建立是因为碰到了hashcode相同而key值不同的情况才建立的,也就是所谓的解决冲突而建立的,而如果数组的长度太短,元素太多,那么所产生的表的长度就会越长,查询效率就会越低,所以这里根据这种情况,利用其时间复杂度曲线,选取一个合适的加载因子计算得到的这个阀值,不过这个阀值的选定并不一定是最好的,这里考虑的是使用空间来换取时间,如果空间很大,这个加载因子可以选的更小,这样冲突的概率就更低,查询效率就会更高。

5.在扩容的时候,为什么还要再计算一次,而不是简单的拷贝到新的数组里面?

扩容的时候再计算的原因是为了把原来的链表长度变的更低,把原来冲突的情况,做进一步的减少,从而提高查找效率。

6. 为什么会把null放在0的位置?

因为我们要依赖key去做hash,而对于null进行hashcode方法的调用,势必会抛出异常(空指针异常),所以针对这种特殊的情况,我们把它放到一个特殊的位置上。个人理解,有不同理解欢迎讨论。

7. 为什么以2倍的原长度来进行扩容呢?

这里为什么选取2倍的长度进行扩容,本人也不知道为什么,可能是通过某种算法计算,认为这种扩容方式可以达到空间最少的浪费,而又可以做到相对较好的解冲突的原因吧。而本人感觉这里可以进行一下优化,我们可以根据我们的业务场景,动态的去优化这个扩容策略,比如里面写一个算法,假如碰到的情景频繁的进行扩容的话,不妨对这种业务情景一次性扩的大一些,而对于较少扩容的情况,是否可以缩减其扩容比例。

8. 为什么会有个modcount,是做什么用的?

对于modcount其使用的时候也就是在迭代器里面用一下,这里主要是防止我们获取到迭代器之后,对Hashmap又进行了操作,而没有重新获取迭代器,造成迭代器中的数据与真实的Hashmap中的数据不一致。

9. 为什么在扩容的时候,会有一个最大值的限制?

这里个人认为是为了取时间和空间的一个平衡,当不再扩容的时候,如果再增加元素冲突的概率就会增加,链表的长度也会增加,不能够再简单的通过计算来得到要查找的元素,而还需要再去遍历链表来进一步查询了。

10. 迭代器怎么实现?

这里既有数组又有链表,所以要注意一下这里的遍历顺序,首先通过循环找到不为空的next,然后遍历next的链表,如果链表到头,则继续遍历数组,找到下一个不为空的位置,这样循环的跑这个过程,直到全部遍历完。这里我们可以看到,这种离散的结构,会碰到很多的空遍历,因为有些数组位置没有元素,其效果不如ArrayList和LinkedList的迭代器。

以上是个人的一些理解,以及学习的过程,如有不同意见欢迎讨论

参考链接:

https://blog.youkuaiyun.com/u011617742/article/details/54576890

https://www.cnblogs.com/ITtangtang/p/3948406.html



`HashMap` 是 Java 集合框架中的一个重要类,用于存储键值对(key-value pairs)。它基于哈希表实现,允许快速查找、插入和删除元素。以下是关于 `HashMap` 的一些核心理解和要点: ### 1. 工作原理 - **哈希函数**:每个键都会通过哈希函数计算出一个哈希码(hashCode),这个哈希码决定了该键应该存放在数组的具体位置上。 - **链地址法处理冲突**:当两个不同的键产生了相同的哈希码时(即发生了碰撞),它们会被放到同一条双向链表或红黑树结构中保存起来。 ### 2. 主要特点 - **非线程安全**:如果多个线程同时修改同一个 HashMap 实例,则可能会导致数据一致性问题。因此,在并发环境中应使用其他替代品如 ConcurrentHashMap 或手动同步。 - **允许 null 键和 null 值**:虽然可以包含任意数量的 null 值,但只能有一个唯一的 null 键。 - **无序性**:遍历顺序与插入顺序无关,因为它内部是以桶(bucket)为基础组织数据的。 ### 3. 性能优势 - **常数时间复杂度 O(1)**:理想情况下对于 get() 和 put() 操作的时间复杂度接近于 O(1),这是因为直接根据 key 计算索引来定位对应的 bucket 节点。 ### 使用示例 ```java import java.util.HashMap; public class Example { public static void main(String[] args) { // 创建一个新的 HashMap 实例 HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("Alice", 25); map.put("Bob", 30); // 获取指定键所关联的值 System.out.println(map.get("Alice")); // 输出:25 // 删除某个条目 map.remove("Bob"); // 检查是否为空 if (!map.isEmpty()) { System.out.println("Map is not empty."); } } } ``` 以上是对 `HashMap` 类的基本介绍及其主要特性的概述。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值