欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > HashMap的数据结构是怎样的?为什么JDK8中要将其转换为红黑树?

HashMap的数据结构是怎样的?为什么JDK8中要将其转换为红黑树?

2024/10/25 21:22:21 来源:https://blog.csdn.net/wls_gk/article/details/141129014  浏览:    关键词:HashMap的数据结构是怎样的?为什么JDK8中要将其转换为红黑树?

HashMap是Java中常用的一种数据结构,它是基于哈希表实现的,用于存储键值对。在JDK8之前,HashMap的数据结构是数组链表的组合。而在JDK8中,当链表的长度超过一定阈值时,会将链表转换为红黑树,这是因为红黑树的查找、插入、删除等操作具有更高的效率。

在本篇博文中,我们将详细介绍HashMap的数据结构、哈希函数的计算方式、数组链表的缺点以及红黑树的优势,同时提供代码块进行分析。

一、HashMap的数据结构

HashMap的底层数据结构是数组,每个数组元素称为桶(bucket)。当插入一个键值对时,首先根据键的哈希值计算出数组的索引,然后将键值对存储在对应的桶中。

public class HashMap<K, V> {// 桶数组transient Node<K,V>[] table;// 静态内部类,用于保存键值对static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;// ...}// ...
}

在JDK8之前,每个桶中存储的是一个链表,通过next指针将键值对连接起来。当多个键的哈希值相同时,它们会被放置在同一个桶中,形成链表。这样,通过遍历链表就可以找到指定键对应的值。

二、哈希函数的计算方式

哈希函数的作用是将任意长度的输入(键)映射为固定长度的输出(数组索引)。在HashMap中,默认的哈希函数是根据键对象的hashCode()方法返回的哈希码进行计算的。

public final int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char[] val = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}

在计算哈希值之后,HashMap会对哈希值进行扰动处理,以减少哈希冲突。具体做法是通过对哈希值进行位运算,使得低位的信息也能参与到哈希值的计算过程中。

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

三、数组链表的缺点

在JDK8之前的HashMap实现中,每个桶中存储的是一个链表。这种数据结构在插入、查找、删除等操作上具有一定的局限性。

1. 查找效率低

链表需要按顺序遍历元素来查找指定键对应的值,即使是最优情况下,也需要遍历链表的所有元素,时间复杂度为O(n)。

2. 插入效率低

当插入一个键值对时,需要遍历链表来判断键是否已经存在,如果存在则更新对应的值,如果不存在则在链表末尾插入新节点。同样,插入操作的时间复杂度也为O(n)。

3. 删除效率低

删除操作需要先遍历链表找到指定键对应的节点,然后将该节点从链表中删除。同样,删除操作的时间复杂度也为O(n)。

综上所述,当链表的长度过长时,HashMap的性能会变得极其低下。在JDK8中,为了提高HashMap在极端情况下的性能,引入了红黑树的概念。

四、红黑树的优势

红黑树是一种自平衡的二叉查找树,具有以下特点:

  • 树的节点可以是红色或黑色。
  • 根节点和叶子节点(null节点)都是黑色。
  • 如果一个节点是红色,则它的子节点必须是黑色。
  • 从根节点到叶子节点的任意路径上,黑色节点的数量是相等的。

红黑树比链表具有更高的查找、插入、删除效率,时间复杂度为O(log n)。

在JDK8中,当链表的长度超过8时,HashMap会将链表转换为红黑树。这样,在大部分情况下,HashMap的性能将得到大幅提升。

五、HashMap在JDK8中的实现逻辑

在JDK8中,当链表的长度超过8时,会触发链表转换为红黑树的操作。

转换过程分为两个步骤:

1. 首先,将链表转换为红黑树

首先,HashMap会将链表中的节点复制到新的红黑树中,并且按照哈希值进行排序。这一步骤的时间复杂度为O(n log n)。

/*** Replaces all linked nodes in bin at index for given hash with* a tree containing all those nodes.*/
final void treeifyBin(Node<K,V>[] tab, int hash) {// ...TreeMap<K,V> newTree = null;for (Node<K,V> e = first; e != null; e = next) {K key = e.key;V value = e.value;if (newTree == null) {newTree = new TreeMap<>(hash, key, value, null);} else {newTree.put(key, value);}}if (newTree != null) {tab[index] = newTree;}
}

2. 然后,将红黑树转换为链表

当红黑树的节点数量小于6时,会触发将红黑树转换为链表的操作。这一步骤的时间复杂度为O(n)。

/*** Returns a list of non-TreeNodes replacing those linked from* this node.*/
final Node<K,V> untreeify(Node<K,V> map) {// ...Node<K,V> hd = null, tl = null;for (Node<K,V> q = map; q != null; q = q.next) {Node<K,V> p = new Node<>(q.hash, q.key, q.value, null);if (tl == null) {hd = tl = p;} else {tl.next = p;p.prev = tl;tl = p;}}return hd;
}

经过转换后,HashMap会在查找、插入、删除等操作时根据键的哈希值来选择使用链表或红黑树,以获得更高的性能。

六、总结

在JDK8中,HashMap的数据结构从数组链表转换为数组红黑树。这一改进使得HashMap在处理哈希冲突、查找、插入、删除等操作时具有更好的性能表现。

通过将链表转换为红黑树,HashMap提高了查找、插入、删除等操作的效率,减少了极端情况下的性能下降。然而,红黑树的插入、删除等操作相对复杂,所以只有在链表长度超过一定阈值时才会触发转换操作。

希望本篇博文对你理解HashMap的数据结构有所帮助。如果有任何疑问或建议,请随时提出。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com