Java HashMap

108 阅读5分钟

What's HashMap?

HashMap是Java中最常用的数据结构之一,它实现了Map接口,允许存储键值对,并提供了快速的查找、插入和删除操作。

HashMap数据结构

HashMap数据结构的详细解释:

  1. 结构:HashMap是基于哈希表实现的,内部使用数组来存储数据。每个元素存储一个键值对,其中键和值都可以为null。HashMap使用哈希算法来计算键的哈希码,然后根据哈希码确定存储位置。
  2. 哈希冲突:由于不同的键可能会映射到相同的哈希码,即产生哈希冲突。HashMap使用链表或红黑树来解决哈希冲突,当链表长度超过阈值(8)时会转换为红黑树,以提高性能。
  3. 容量和负载因子:HashMap有两个重要的参数,容量和负载因子。容量表示HashMap的桶数组的大小,默认为16;负载因子表示在(桶数组达到容量负载因子时)(桶数组达到容量*负载因子时)会触发扩容操作,默认为 0.750.75(16*0.75=12,默认map容量到12会扩容一次)。
  4. put操作:向HashMap中插入键值对时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,直接插入;如果桶位置已经有元素,判断是否为相同的键,是则更新值,不是则通过链表或红黑树解决冲突。
  5. get操作:根据键查找值时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,返回null;如果桶位置有元素,判断是否为相同的键,是则返回对应的值,不是则通过链表或红黑树查找。
  6. remove操作:根据键删除值时,首先计算键的哈希码,然后根据哈希码找到对应的桶位置。如果桶位置为空,不做任何操作;如果桶位置有元素,判断是否为相同的键,是则删除对应的键值对,不是则通过链表或红黑树查找并删除。

时间复杂度

HashMap的时间复杂度为 O(1)O(1),具有快速的查找、插入和删除操作。但需要注意的是,HashMap不是线程安全的,如果在多线程环境下使用,需要考虑线程安全性。如果需要线程安全的Map,可以使用ConcurrentHashMap

hashCode(散列码)

利用散列码来取代键缓慢的线性搜索。散列码是 '相对唯一的'、用以代表对象的int值,也就意味着会重复。它是通过对象的某些信息进行转换生成的。hashCode是根类Object的方法,因此所有Java对象都能产生散列码。

HashMap就是利用对象的hashCode()进行快速查询的。如果键被用于HashMap,那么必须重写equals和hashCode().

常见的哈希冲突采用的解决方案是多次散列。

常见问题

为什么使用链表解决冲突?

在哈希表中,当多个键映射到同一个哈希桶时,就会发生冲突。解决冲突的一种方法是使用链表。使用链表解决冲突的原因包括:

  1. 简单有效:链表是一种简单而有效的数据结构,可以轻松地将多个元素存储在同一个哈希桶中。
  2. 节省空间:相比于其他解决冲突的方法,如开放寻址法,链表不需要额外的空间来存储冲突的元素,只需要在每个哈希桶中存储指向链表头部的指针即可。
  3. 灵活性:链表可以动态地调整大小,适应不同数量的冲突元素,而不会浪费过多的空间。
  4. 时间复杂度:使用链表解决冲突时,查找、插入和删除操作的时间复杂度都是O(n)O(n),因为只需要遍历链表的常数个节点即可完成操作。如果遍历常数个长度那么时间复杂度可以到 O(1)O(1),这种情况是最优的。

简言之,链表的阈值 8≤8 就不会转换为其他数据结构,因为它的时间复杂度是随着链表的长度 nn 变化的,当 nn 越大,链表这种数据结构性能越差。

为什么使用链表后又转换为红黑树?

在一些情况下,使用链表解决冲突可能会导致性能下降,因为链表的查找、插入和删除操作的时间复杂度都是O(n)O(n),其中 nn 是链表的长度。当哈希表中的某个哈希桶中的元素数量非常大时,链表的性能可能会变得很差。

为了解决这个问题,一种常见的做法是将链表转换为红黑树。红黑树是一种自平衡的二叉搜索树,具有较快的查找、插入和删除操作的时间复杂度,为 O(logn)O(\log n) ,其中n是树中节点的数量。 当哈希表中的某个哈希桶中的元素数量超过一个阈值时,可以将链表转换为红黑树。这样可以提高哈希表的性能,减少查找、插入和删除操作的时间复杂度,同时保持较小的空间复杂度。红黑树的自平衡特性也能够确保树的高度保持在较低的水平,进一步提高了性能。

Note