HashMap实现原理
HashMap是Java中用于存储键值对的高性能数据结构。它基于哈希表(hash table)实现,用于快速查找、插入和删除操作。下面是HashMap的简要实现原理:
哈希函数: HashMap使用哈希函数将键映射到数组的特定位置,这个位置称为桶(bucket)。哈希函数的目标是将键的范围映射到数组的索引范围内。Java中的Object类提供了hashCode()
方法,它返回一个用于标识对象的整数哈希码。
桶和链表/树: 数组的每个桶可能存储一个或多个键值对。当多个键映射到相同的桶时,就会发生哈希冲突。为了解决冲突,HashMap会在每个桶中使用链表(或红黑树)来存储相同桶位置上的键值对。当链表长度达到一定阈值时,链表可能会转换为树,以提高性能。
存储和获取: 存储时,HashMap会计算键的哈希码,然后通过哈希函数找到对应的桶位置,如果桶为空,则直接将键值对存储在该桶中。如果桶中已经存在键值对,HashMap会遍历链表或树来查找是否有相同的键。获取时,HashMap也会计算键的哈希码,找到对应的桶位置,然后遍历链表或树来找到具体的值。
扩容和重新哈希: 当HashMap中的键值对数量增多,桶中链表的长度可能会变长,导致查找效率降低。为了维护性能,HashMap会在适当的时候进行扩容操作。扩容时,HashMap会创建一个更大的数组,然后将现有的键值对重新分配到新的桶中,这个过程称为重新哈希。
加载因子: 加载因子是一个比例,表示已存储键值对数量与桶总数之间的关系。当加载因子超过一定阈值时,就触发扩容。较高的加载因子可以减少空间浪费,但可能导致冲突增多。较低的加载因子可以减少冲突,但可能会浪费空间。在默认情况下,Java的HashMap加载因子为0.75。
综上所述,HashMap通过哈希函数和桶来实现高效的键值对存储和查找,通过链表和树来解决冲突,通过扩容和重新哈希来维护性能。这使得HashMap在很多应用场景下都能够快速、高效地进行数据操作。
示意图
1、示意图1
2、示意图2
HashMap数组里的数据存储的是什么
在Java中,HashMap是一种用于存储键-值对数据的数据结构。在HashMap数组中,存储的是键值对的信息。每个键值对由一个键(key)和一个对应的值(value)组成。HashMap使用哈希函数将键映射到数组中的特定位置,这样可以快速地通过键查找对应的值。
简而言之,HashMap数组中存储的是键值对的信息,其中键用于唯一标识一个值,而值则是与键相关联的数据。这种数据结构可以高效地实现通过键查找值的操作。
说白了,存储的就是节点(Node)。
节点源码:java.util.HashMap.Node
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node implements Map.Entry {
final K key;
V value;
Node next; //指向下一个节点
如果下标冲突,那么就是多个节点组成一个链表。
数组的某个下标:只包含一个数据和包含多个数据的区别
在HashMap的数组中,每个下标位置存储的数据可以包含一个或多个键值对。让我解释一下单个数据和多个数据的区别:
单个数据:
如果数组的某个下标位置只包含一个数据,这通常意味着在哈希表中没有冲突发生。也就是说,经过哈希函数计算后,该数据的键经过哈希映射得到的下标位置没有与其他键产生冲突,所以这个位置只存储了一个键值对。
多个数据:
当多个键通过哈希函数映射到了相同的下标位置时,就会发生哈希冲突。这意味着同一个数组下标位置需要存储多个键值对。为了解决冲突,HashMap使用一种链表或者更优化的数据结构(如红黑树)来存储相同下标位置的多个键值对。这样,即使多个键映射到同一个下标,也能够通过链表或树结构进行高效的查找和插入操作。
总之,在HashMap的数组中,某个下标位置只包含一个数据表示没有冲突,而包含两个或更多数据则表示发生了哈希冲突,需要使用链表或树来存储多个键值对。
示意图
假设我们有一个包含5个位置的HashMap数组,每个位置可以存储一个键值对。现在我们来看两种情况:一个位置只包含一个数据和一个位置包含两个数据的情况。
Index 0: [Key1: Value1]
Index 1:
Index 2:
Index 3:
Index 4:
在这种情况下,只有一个键值对被存储在数组的第一个位置(下标0)。
Index 0: [Key1: Value1] -> [Key6: Value6]
Index 1:
Index 2: [Key2: Value2]
Index 3:
Index 4:
在这种情况下,有两个键值对被存储在数组的第一个位置(下标0),因为Key1和Key6经过哈希函数后映射到了同一个位置。此时,使用链表或树结构来存储这两个键值对,以便可以高效地查找和插入。
请注意,这只是一个简化的示意图,实际情况可能更加复杂,因为HashMap会自动进行扩容等优化操作。如果你需要更详细的图示,你可以在网上搜索关于HashMap的示意图和哈希冲突解决方法的图解。
如果key冲突,新节点是查到前面还是插到后面?
在HashMap中,当发生键(key)冲突时,新节点通常会被插入到已存在节点的后面,也就是通过链表或其他适当的数据结构连接到冲突位置的后面。
当多个键经过哈希函数计算后映射到同一个数组下标位置时,HashMap会在该位置维护一个链表(或者在某些情况下,使用红黑树等更高效的数据结构)。每个链表节点都存储一个键值对。如果新节点与已存在节点发生冲突,新节点将被插入到链表的末尾,而不是前面。这种做法确保了插入操作的稳定性,并保持了之前的键值对顺序。
然而,需要注意的是,随着不断插入新的节点,链表可能会变得很长,导致查找效率降低。为了应对这种情况,Java的HashMap在一些条件下会进行重新哈希(rehashing)和转换链表为红黑树等优化操作,以提高性能和保持合理的查找速度。
示意图
假设我们有一个HashMap数组,数组长度为5,下标从0到4。在下标0位置发生了哈希冲突,已经存在一个键值对 [Key1: Value1],现在要插入一个新的键值对 [Key6: Value6]。
初始状态:
Index 0: [Key1: Value1]
Index 1:
Index 2:
Index 3:
Index 4:
在发生冲突后,数组下标0位置的链表如下所示(箭头表示链表连接):
Index 0: [Key1: Value1] -> [Key3: Value3]
Index 1:
Index 2:
Index 3:
Index 4:
现在要插入新的键值对 [Key6: Value6],它将被插入到链表的末尾,如下所示:
Index 0: [Key1: Value1] -> [Key3: Value3] -> [Key6: Value6]
Index 1:
Index 2:
Index 3:
Index 4:
请注意,这是一个简化的示意图,实际情况中,链表可能会根据实现和数据情况进行优化。另外,随着链表长度的增加,一些实现可能会触发优化操作,如链表转换为树,以保持性能。