说明:
在这篇文章中,博主对于HashMap的结构、部分源码、以及部分的方法,通过举例和图片进行了简要及形象的介绍,旨在帮助大家对于HashMap拥有一个初步的理解和形象的认识,为后续更加深入的理解HashMap打下相对坚实的基础。
博主也会在后面对HashMap的底层实现结构,扩容机制,树化原理,源码解析做出更为详细精确的解读,大家敬请关注后续博主发布的博客,同时也希望各位大佬多多支持,指正不足。
1. HashMap引入
在HashMap中,是以键值对的方式来进行数据的存储,具有双列存储的特点。所谓键值对,实际上就是两个存在相互关系的值key,value。其中的key(键)对应一个value(值),通过一个特定的key,我们能够访问到其所对应的value值。用一个形象通俗的例子来说明,在每个人的身上都具有一些特定的属性,且这些属性往往会对应特定的值,例如某人的 姓名:张三,体重:60kg, 身高:175cm,爱好语言:Java,这就是典型的键值对。
2. Java中对HashMap的实现
从HashMap的继承关系图来看,在Java中,HashMap实现了Map接口。在Map接口中,使用的是具有映射关系的 key-value 键值对来存储数据,这也是HashMap结构的基础。
补充:Map接口的特性
Map接口拥有如下的特性:
(1) key和value均可以为任意类型的数据
(2) Map中的key不允许重复,可以为null (这个特性也影响了HashMap的扩容机制)
(3) Map中的value允许重复,可以为null
3. HashMap结构简述
HashMap中每个键值对的结构
从HashMap的源码中我们可以看出,HashMap的每一个键值对都实现了Map接口中的Entry,故每个Node都拥有hash、key、value、next这四个属性
(1) hash: 用于存放每个Node的key值经过hash算法后得到的值
(2) key: 用于存放每个键值对对应的key值
(3) value: 用于存放键值对中要保存的数据
(4) next: 当有多个相同的hash值的Node时,这些Node就会相互连接形成链表,next即用于 指向下一个Node
HashMap整体结构简述
在jdk1.8之前,HashMap主要由数组和链表组成。HashMap的数组的每一个下表即可以看成可是一个哈希桶,每一个哈希桶对应着一个Node(即entry)的key值在经过哈希算法后得到的哈希值。当我们在对HashMap中查找某个Node时,就会根据Node所对应的哈希值来进行查找,故此时查找的时间复杂度一般为O(1).根据上述提到的在Map中的key不允许重复的特性,当我们往HashMap中添加过多的重复元素时,就会使得HashMap中链表的长度不断延长,这将导致性能问题,因为查找操作的时间复杂度会退化到O(n)。简单的结构如下:
到jdk1.8往后,HashMap引入了红黑树来更好的处理哈希冲突,可以保证在最坏情况下的查找、插入和删除操作的时间复杂度为O(log n)。即在jdk1.8之后,HashMap是使用的数组 + 链表 + 红黑树的结构来构建的。
jdk1.8后的HashMap详细的扩容机制、树化的条件、以及源码的详细解读,博主会在后面的文章中更新,也请各位大佬多多鼓励支持
补充简要介绍红黑树
红黑树(Red-Black Tree)是一种自平衡的二叉查找树,其中每个节点都有一个颜色属性,可以是红色或黑色。红黑树的名称来源于这两种颜色的使用。它是一种非常有效的数据结构,用于存储有序的数据,并且可以快速地进行插入、删除和查找操作。
以下是红黑树的一些关键特性和规则:
每个节点要么是红色,要么是黑色。
根节点是黑色的。
所有叶子节点(NIL节点,空节点)都是黑色的。
每个红色节点的两个子节点都是黑色的(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
4. HashMap相关操作
在Java中,封装了对HashMap的相关操作
向HashMap中添加新的键值对 - put()
添加键值对 k-v 流程
1. 将传入的数据键值对 k-v 封装成一个Node节点
2. 在向HashMap中添加新的 k-v 时,HashMap会将传入的key进行哈希算法运算,得到一个哈希值
3. 通过得到的哈希值来对应数组中的一个对应的下标,即哈希桶
4. 将Node节点存入到哈希桶中
相关代码操作
运行结果
put() 方法源码
在源码中可以看出,在 put() 方法中使用到了 putval() 方法来添加键值对,关于 putval() 方法、HashMap扩容机制、以及源码的详细解读,博主会在之后的进行及时的更新。
5. 遍历HashMap中的键值对
方式一:使用 EntrySet 进行遍历
在前面我们有提到,在HashMap中,每一个键值对会被分装为Node节点,而Node节点又实现了Entry接口,故实际上每一个键值对是被封装为Entry。在封装成Entry之后,HashMap会将每一个Entry存储到EntrySet集合中。因此,我们可以通过EntrySet集合来取出HashMap中存储的所有键值对数据。
代码实现
for (Object o : map.entrySet()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) o;
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key = " + key + "; " + "value = " + value);
}
运行结果
、
方式二:使用使用Iterator迭代器进行遍历
在EntrySet集合中,还封装了一个迭代器,可用于遍历取出HashMap中的每一个键值对
Iterator源码解析
从上述Iterator源码中我们可以看到,在Iterator中调用了EntryIterator方法,而EntryIterator类也是实现了Iterator接口,故我们在调用Iterator迭代器时,真正调用到的是Iterator接口的实现方法。在EntryIterator实现类中,实现了Iterator接口的 next() 方法,用于从EntrySet中取出每一个Entry(即键值对)。同时,在Iterator接口中,还会调用 hashNext() 方法来确定EntrySet中是否还存在Entry(键值对)。
代码实现
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) { //通过循环来取出EntrySet中的Entry
Map.Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println("iterator_key = " + key + "; " + "iterator_value = " + value);
}
运行结果
总结
HashMap 是一种基于哈希表的 Java 数据结构,用于存储键值对,其中键是唯一的。它通过哈希函数快速定位键值对,提供平均 O(1) 时间复杂度的查找、插入和删除操作。