Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)

从JDK 1.7到JDK 1.8,HashMap在底层实现上发生了显著的变化,

主要体现在数据结构、链表插入方式、哈希算法、扩容机制以及并发性方面。

以下是具体的变化点:

1. 数据结构的变化

  • JDK 1.7:HashMap的底层数据结构是数组+单向链表。当哈希冲突发生时,新的元素会插入到链表的头部(头插法)。
  • JDK 1.8:HashMap的底层数据结构变为数组+链表/红黑树。当链表长度超过一定阈值(默认为8)时,链表会转换成红黑树,以提高查询效率。这种变化使得HashMap在处理大量数据时能够保持较好的性能。

2. 链表插入方式的变化

  • JDK 1.7:链表插入使用的是头插法,即新的元素会被插入到链表的头部。
  • JDK 1.8:链表插入使用的是尾插法。这是因为JDK 1.8在插入元素时需要判断链表长度,尾插法便于统计链表元素个数。同时,尾插法也避免了头插法可能导致的链表反转问题。

3. 哈希算法的变化

  • JDK 1.7:哈希算法相对复杂,涉及多种右移和位运算操作。
  • JDK 1.8:哈希算法进行了简化。这是因为JDK 1.8引入了红黑树,可以在一定程度上弥补简化哈希算法可能带来的散列性降低问题,从而节省CPU资源。

4. 扩容机制的变化

  • JDK 1.7:扩容时,会重新创建一个更大的数组,并将原数组中的元素逐个添加到新数组中。这个过程比较耗费时间和性能。
  • JDK 1.8:扩容机制得到了优化。当达到扩容条件时(容量*负载因子超过阈值),会创建一个大小为原数组两倍的新数组,并采用更高效的方式迁移数据。此外,JDK 1.8还引入了“树化”的概念,即在扩容时,链表长度达到阈值的桶会直接转换为红黑树,以减少扩容操作的时间复杂度。

5. 并发性的改进

  • JDK 1.7:HashMap本身是非线程安全的,在多线程环境下使用时需要额外的同步机制来保证数据一致性。
  • JDK 1.8:虽然HashMap本身仍然是非线程安全的,但JDK 1.8通过一些机制(如synchronized关键字、CAS操作等)提高了其在多线程环境下的性能。然而,对于需要线程安全的场景,仍然建议使用ConcurrentHashMap。

代码讲解

下面是一些简化的源码片段和注释,用于说明JDK 1.7和JDK 1.8中HashMap的关键部分。

JDK 1.7 HashMap 简化源码
// JDK 1.7 HashMap 的简化版本
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    
    // 省略了大部分成员变量和方法...

    // 存储元素的数组
    transient Entry<K, V>[] table;

    // Entry 是 HashMap 的一个内部类,表示一个键值对
    static class Entry<K, V> implements Map.Entry<K, V> {
        final K key;
        V value;
        Entry<K, V> next;
        // 省略了构造方法和其它方法...
    }

    // 省略了其它方法...

    // put 方法(简化版)
    public V put(K key, V value) {
        // 对 key 进行哈希运算,定位到数组中的某个位置
        int hash = hash(key);
        int i = indexFor(hash, table.length);

        // 遍历链表,查看是否已存在相同的 key
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && (e.key.equals(key) || key.equals(e.key))) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // 如果不存在,则添加新的 Entry 到链表中
        addEntry(hash, key, value, i);
        return null;
    }

    // 添加新的 Entry 到数组中(链表形式)
    void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K, V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        // 如果数组中的元素数量超过阈值,则进行扩容
        if (size++ > threshold)
            resize(2 * table.length);
    }

    // 省略了其它方法...
}
JDK 1.8 HashMap 简化源码
// JDK 1.8 HashMap 的简化版本
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    
    // 省略了大部分成员变量和方法...

    // 存储元素的数组(在 JDK 1.8 中,这个数组可能存储 Node 或 TreeNode)
    transient Node<K, V>[] table;

    // Node 是 JDK 1.8 中新引入的一个内部类,用于替代 Entry
    static class Node<K, V> implements Map.Entry<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;
        // 省略了构造方法和其它方法...
    }

    // 当链表长度超过8时,会转换为红黑树,TreeNode 是红黑树的一个节点
    static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {
        TreeNode<K, V> parent;
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev;
        // 省略了红黑树相关的其它方法和成员变量...
    }

    // 省略了其它方法...

    // put 方法(简化版)
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    // 实际的 put 逻辑(简化版)
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K, V>[] tab;
        Node<K, V> p;
        int n, i;

        // 如果数组为空,则进行初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        // 定位到数组中的某个位置,如果为空则直接插入新的 Node
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 如果不为空,则可能是链表或红黑树
            Node<K, V> e;
            K k;
            // 省略了部分逻辑,包括树化处理和链表遍历...

            // 如果找到了相同的 key,则更新 value 并返回旧值
            if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }

            // 如果没找到相同的 key,则将新的 Node 插入到链表的末尾(或树中)
            // 省略了具体的插入逻辑...
        }

        // 省略了其它逻辑,包括扩容和计数更新...
        return null;
    }

    // 省略了其它方法...
}

注释说明

  • 在JDK 1.7中,HashMap使用Entry内部类来表示键值对,而在JDK 1.8中,引入了Node内部类来替代Entry
  • JDK 1.8中新增了TreeNode内部类,用于在链表长度超过8时将链表转换为红黑树,以提高性能。
  • JDK 1.8中的put方法逻辑更加复杂,因为它需要处理链表和红黑树两种情况。
  • 扩容机制在JDK 1.8中也有所改进,使得扩容过程更加平滑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值