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的数据结构有所帮助。如果有任何疑问或建议,请随时提出。

  • 27
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HashMap 是一种哈希表实现的映射。它存储键值对,允许用户快速查找特定键对应的值。 HashMap 底层是通过数组和链表实现的。它将键哈希成一个整数,然后将键值对存储在数组中的对应位置。如果多个键值对映射到了同一个数组位置,它们就会按照链表的形式连接起来。 在 JDK8 中,HashMap 做了以下优化: 1. 当链表长度大于某个阈值(默认为 8)时,会自动转换红黑树。这样可以使查询的时间复杂度从 O(n) 降低到 O(log n)。 2. 当哈希表中的实际大小(键值对数量)超过哈希表数组的大小时,会自动将哈希表数组的大小增加一倍。这样可以减少碰撞的次数,提高查询效率。 3. 引入了“红色哈希码”(fusing hash code)技术,可以通过一个状态位来标识一个节点是否被搬迁过。这样可以在查询时避免对节点的额外访问,提高查询效率。 ### 回答2: HashMap是一种常用的键值对存储结构,其底层实现原理是哈希表。具体而言,HashMap使用了一个数组和链表(或红黑树)的数据结构。当元素插入HashMap中时,会根据元素的哈希值计算出对应的索引位置,并将其插入到该索引位置对应的桶中。如果多个元素的哈希值相同,它们会通过链表(JDK8之前)或红黑树JDK8之后)的形式存储在同一个桶中,以避免哈希冲突。 JDK8对HashMap进行了一些优化,以提高其性能和效率。其中包括以下几个方面: 1. 红黑树JDK8在HashMap的实现中引入了红黑树的概念。当链表长度超过一定阈值(默认为8)时,链表会被转换红黑树。这样可以大大提高在大型HashMap中进行查找、删除和插入操作的效率。 2. 数组扩容:JDK8在HashMap的数组扩容时,采用了一种更高效的方式。在JDK8之前,每次扩容都需要重新计算元素的哈希值并重新放置到新的数组中,而JDK8则通过利用元素的高位哈希值来加快定位。 3. 链表转换红黑树的优化:在JDK8中,当链表长度小于阈值(默认为6)时,如果需要插入新元素,HashMap会优先在链表的末尾插入,而不会转换红黑树。这样可以避免在链表长度较小的情况下浪费内存和时间。 总的来说,JDK8对HashMap进行了一些重要的优化,包括引入红黑树、改进数组扩容和链表转换红黑树的策略等,以提高其性能和效率。 ### 回答3: HashMap 是一种常用的数据结构,它是基于哈希表实现的。底层实现原理是使用数组和链表(或红黑树)的组合来存储数据,通过哈希值的计算将数据映射到数组的索引位置,当发生哈希冲突时,会使用链表或红黑树来解决冲突。 在 JDK8 中,HashMap 做了一些优化以提高性能和减少内存消耗。 1. 数组+链表+红黑树的存储结构:当链表长度超过一定阈值(默认为 8)时,链表会转换红黑树,这样可以减少搜索时间复杂度。 2. 节点的存储方式优化:在 JDK8 之前,HashMap 中每个节点都是一个独立的对象,包含了 key、value、next 和 hash 等属性。在 JDK8 中,当链表转化为红黑树时,为了节省内存空间,仅使用红黑树节点存储键值对,减少了额外的存储消耗。 3. 红黑树的自平衡能力:JDK8 中的红黑树实现了更加高效的自平衡算法,能够快速调整树的结构,以维持树的平衡,提高查询、插入和删除操作的效率。 4. 增强了扩容机制:JDK8 在扩容时,不再像以前一样重新计算每个元素的哈希值和索引位置,而是利用高位运算,减少了哈希碰撞的可能性,提高了扩容时的效率。 5. 实现了红黑树节点的统一化:红黑树节点和链表节点使用相同的数据结构,这样可以减少代码的复杂性,提高了代码的维护性。 总之,JDK8 在 HashMap 的底层实现上做了一些优化,包括使用红黑树代替链表来解决哈希冲突、优化节点的存储方式、增强扩容机制和提高红黑树的自平衡能力等,以提高HashMap的性能和减少内存消耗。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值