What's HashMap?
HashMap是Java中最常用的数据结构之一,它实现了Map接口,允许存储键值对,并提供了快速的查找、插入和删除操作。
HashMap数据结构
HashMap数据结构的详细解释:
- 结构:HashMap是基于哈希表实现的,内部使用数组来存储数据。每个元素存储一个键值对,其中键和值都可以为null。HashMap使用哈希算法来计算键的哈希码,然后根据哈希码确定存储位置。
- 哈希冲突:由于不同的键可能会映射到相同的哈希码,即产生哈希冲突。HashMap使用链表或红黑树来解决哈希冲突,当链表长度超过阈值(8)时会转换为红黑树,以提高性能。
- 容量和负载因子:HashMap有两个重要的参数,容量和负载因子。容量表示HashMap的桶数组的大小,默认为16;负载因子表示在会触发扩容操作,默认为 (16*0.75=12,默认map容量到12会扩容一次)。
- put操作:向HashMap中插入键值对时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,直接插入;如果桶位置已经有元素,判断是否为相同的键,是则更新值,不是则通过链表或红黑树解决冲突。
- get操作:根据键查找值时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,返回null;如果桶位置有元素,判断是否为相同的键,是则返回对应的值,不是则通过链表或红黑树查找。
- remove操作:根据键删除值时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,不做任何操作;如果桶位置有元素,判断是否为相同的键,是则删除对应的键值对,不是则通过链表或红黑树查找并删除。
时间复杂度
HashMap的时间复杂度为 ,具有快速的查找、插入和删除操作。但需要注意的是,HashMap不是线程安全的,如果在多线程环境下使用,需要考虑线程安全性。如果需要线程安全的Map,可以使用ConcurrentHashMap。
hashCode(散列码)
利用散列码来取代键缓慢的线性搜索。散列码是 '相对唯一的'、用以代表对象的int值,也就意味着会重复。它是通过对象的某些信息进行转换生成的。hashCode是根类Object的方法,因此所有Java对象都能产生散列码。
HashMap就是利用对象的hashCode()进行快速查询的。如果键被用于HashMap,那么必须重写equals和hashCode().
常见的哈希冲突采用的解决方案是多次散列。
常见问题
为什么使用链表解决冲突?
在哈希表中,当多个键映射到同一个哈希桶时,就会发生冲突。解决冲突的一种方法是使用链表。使用链表解决冲突的原因包括:
- 简单有效:链表是一种简单而有效的数据结构,可以轻松地将多个元素存储在同一个哈希桶中。
- 节省空间:相比于其他解决冲突的方法,如开放寻址法,链表不需要额外的空间来存储冲突的元素,只需要在每个哈希桶中存储指向链表头部的指针即可。
- 灵活性:链表可以动态地调整大小,适应不同数量的冲突元素,而不会浪费过多的空间。
- 时间复杂度:使用链表解决冲突时,查找、插入和删除操作的时间复杂度都是,因为只需要遍历链表的常数个节点即可完成操作。如果遍历常数个长度那么时间复杂度可以到 ,这种情况是最优的。
简言之,链表的阈值 就不会转换为其他数据结构,因为它的时间复杂度是随着链表的长度 变化的,当 越大,链表这种数据结构性能越差。
为什么使用链表后又转换为红黑树?
在一些情况下,使用链表解决冲突可能会导致性能下降,因为链表的查找、插入和删除操作的时间复杂度都是,其中 是链表的长度。当哈希表中的某个哈希桶中的元素数量非常大时,链表的性能可能会变得很差。
为了解决这个问题,一种常见的做法是将链表转换为红黑树。红黑树是一种自平衡的二叉搜索树,具有较快的查找、插入和删除操作的时间复杂度,为 ,其中n是树中节点的数量。 当哈希表中的某个哈希桶中的元素数量超过一个阈值时,可以将链表转换为红黑树。这样可以提高哈希表的性能,减少查找、插入和删除操作的时间复杂度,同时保持较小的空间复杂度。红黑树的自平衡特性也能够确保树的高度保持在较低的水平,进一步提高了性能。
Note
-
see more:算法的时间与空间复杂度
-
红黑树 see more:todo....