介绍
HashMap是Java中最常用的数据结构之一,它实现了Map接口,用于存储键值对。
在JDK1.8之前,HashMap使用的是数组和链表的组合来实现。数组被分成一个个称为桶(bucket)的单元,每个桶可以存储多个键值对。当我们向HashMap中put一个键值对时,首先会根据键的hashcode值计算出对应的桶的索引位置,然后将该键值对存储在该桶中。
如果多个键值对计算得到的桶索引相同,则会以链表的方式存储在同一个桶中。链表中的每个节点都包含一个键值对,并且通过比较键的equals方法来判断键是否相等。当我们根据键获取值时,HashMap会遍历链表,找到匹配的键值对。
然而,由于HashMap使用链表来处理冲突,当存储大量键值对时,链表的长度会越来越长,导致查找效率降低,即时间复杂度变为O(n)。此外,由于链表无序,查找效率更低。这也是为什么在JDK1.8之前的HashMap在处理大量数据时性能较差的原因。
为了解决这个问题,JDK1.8中对HashMap进行了优化,引入了红黑树的概念。当链表长度超过阈值(默认为8)时,链表会自动转化为红黑树,从而加快查找速度。红黑树是一种平衡二叉搜索树,具有较高的查找效率。
因此,在JDK1.8之后的HashMap中,当链表长度超过阈值时,由链表转换为红黑树,大大提高了HashMap的性能。此外,JDK1.8还对HashMap的实现进行了一系列的优化,如使用位运算代替取余操作来计算桶的索引,提高了速度和效率。
总结起来,JDK1.8之前的HashMap使用数组和链表的组合来实现,处理大量数据时性能较差。而JDK1.8之后的HashMap引入了红黑树的概念,提高了查找效率,使其更加适合存储和操作大量数据。
存入数据时
-
首先,根据键的哈希码(hash code)通过哈希函数(hash function)计算出键的索引(index)。哈希函数将哈希码映射到哈希表的槽位(bucket)上。
-
如果槽位是空的,直接将键值对存储在该槽位上。
-
如果槽位已经有其他键值对,可能发生哈希碰撞(hash collision),即两个键具有相同的哈希码。解决哈希碰撞的方法有多种,最常用的是链地址法(chaining),即在槽位上维护一个链表,将发生碰撞的键值对串联起来。
-
当链表长度超过一定阈值(如8)时,可以考虑将链表转换为树结构,以提高查找效率。转换为树的过程如下:
a. 创建一个根节点,将原链表的第一个键值对作为根节点的元素。
b. 遍历链表中的每个键值对,将其插入到树中的正确位置上。
c. 在插入过程中,根据键的比较结果,将键值对插入到树的左子树或右子树中。
d. 当树的大小达到一定阈值(如6)时,将树平衡化以维持树的性能。
在哈希表中转换链表为树的过程,可以提高查找效率,特别是在槽位上的键值对数量较多时。
获取数据时
当我们要获取 HashMap 中的数据时,它首先使用 key 的哈希值来确定数据在数组中的索引位置。然后,它会在该索引位置上的链表中遍历,直到找到对应的 key。具体的流程如下:
- 首先,HashMap 根据 key 的哈希值计算出存储位置(在数组中的索引)。
- 如果该位置上的链表为空,则说明没有找到对应的数据,返回 null。
- 如果该位置上的链表不为空,则遍历链表中的每个节点。
- 对比每个节点的 key 和目标 key 是否相等,如果相等,则找到了对应的数据,返回该节点的值。
- 如果遍历完整个链表都没有找到对应的数据,则说明 HashMap 中不存在该 key,返回 null。
总结起来,HashMap 的获取数据流程主要是根据 key 的哈希值确定数据在数组中的位置,然后在该位置上的链表(或红黑树)中遍历查找对应的数据。
hashmap的扩容机制
HashMap的扩容机制是指当HashMap中的元素个数超过了负载因子(load factor)乘以当前容量时,就会触发扩容操作。
负载因子是一个介于0和1之间的值,表示HashMap在容量自动扩容之前可以达到的填充因子的比例。当HashMap中的元素个数超过负载因子乘以当前容量时,就会触发扩容操作。
扩容操作会创建一个新的更大的数组,然后将原来数组中的元素重新计算哈希值,并放入新的数组对应的位置中。这个过程称为重新哈希(rehashing)。
在扩容的过程中,HashMap会将元素重新分布到新的数组中,这有可能导致一些元素的哈希冲突,即多个元素映射到了同一个位置上。为了解决哈希冲突,HashMap使用了链表的方式来存储相同位置的元素。当元素被重新分配到新数组的相同位置时,它会被添加到链表的末尾。
为了优化性能,当链表的长度超过一定阈值(8)时,链表会转化成红黑树。这样可以在查找、插入和删除元素时,减少平均时间复杂度。
通过扩容操作,HashMap可以保持一个较低的填充因子,从而提高查询和插入操作的性能。
需要注意的是,扩容操作是一个比较耗时的操作,因为需要重新计算哈希值和重新分配元素。所以,在使用HashMap时,我们应该尽量预估元素的个数,并设置合适的初始容量和负载因子,以减少扩容的频率。