Java HashMap 底层原理

一、JDK 1.7的HashMap源码解析

1. 数据结构
  • Entry类 :链表节点定义,含key, value, hash, next指针
    源码节点:

    java 复制代码
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        // 构造方法及get/set方法省略
    }
2. 插入过程(头插法)
  • put方法 :计算哈希→处理冲突→扩容
    源码节点:

    java 复制代码
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) inflateTable(threshold); // 延迟初始化
        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 && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }
        addEntry(hash, key, value, i); // 头插新节点
    }
3. 扩容死循环问题
  • transfer方法 :头插法导致链表反转
    源码节点:

    java 复制代码
    void transfer(Entry[] newTable) {
        for (Entry<K,V> e : table) {
            while (e != null) {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i]; // 头插法
                newTable[i] = e;
                e = next;
            }
        }
    }

    问题:多线程扩容时头插法导致链表成环。


二、JDK 1.8的HashMap改进

1. 数据结构升级
  • Node与TreeNode :链表节点与红黑树节点
    源码节点:

    java 复制代码
    // 普通链表节点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    }
    // 红黑树节点
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent, left, right, prev;
        boolean red;
    }
2. 插入优化(尾插法)
  • putVal方法 :尾插链表,树化逻辑
    源码节点:

    java 复制代码
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null); // 直接插入
        else {
            Node<K,V> e; K k;
            if (p.hash == hash && ((k = p.key) == key || key.equals(k)))
                e = p;
            else if (p instanceof TreeNode) // 红黑树插入
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null); // 尾插法
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash); // 链表转树
                        break;
                    }
                    // ...省略冲突处理
                }
            }
        }
    }
3. 哈希扰动优化
  • hash方法 :简化扰动逻辑
    源码节点:

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

三、ConcurrentHashMap的线程安全实现

1. JDK 1.7的分段锁
  • Segment类 :继承ReentrantLock,锁分段
    源码节点:

    java 复制代码
    static final class Segment<K,V> extends ReentrantLock {
        transient volatile HashEntry<K,V>[] table;
        // put方法加锁
        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(...);
            try {
                // ...插入逻辑
            } finally {
                unlock();
            }
        }
    }
2. JDK 1.8的CAS+synchronized
  • Node与CAS操作 :无锁化初始化与插入
    源码节点:

    java 复制代码
    // 初始化表(CAS控制并发)
    private final Node<K,V>[] initTable() {
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0) Thread.yield();
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    // 创建新数组
                } finally {
                    sizeCtl = sc;
                }
            }
        }
    }
    
    // putVal中的synchronized块
    synchronized (f) {
        if (tabAt(tab, i) == f) {
            if (fh >= 0) {
                // 链表插入
            } else if (f instanceof TreeBin) {
                // 红黑树插入
            }
        }
    }
3. 扩容协作
  • helpTransfer方法 :多线程协助数据迁移
    源码节点:

    java 复制代码
    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        if (tab != null && f instanceof ForwardingNode) {
            while (nextTab == nextTable && table == tab) {
                // 协助扩容线程完成数据迁移
            }
        }
    }

四、核心差异总结

特性 JDK 1.7 JDK 1.8
HashMap数据结构 数组+单向链表(Entry类) 数组+链表/红黑树(Node/TreeNode类)
ConcurrentHashMap锁 分段锁(Segment类) 细粒度锁(synchronized+CAS)
插入方式 头插法(transfer方法成环) 尾插法(putVal方法避免死循环)
哈希计算 4次扰动(复杂位运算) 1次扰动(h ^ (h >>> 16))

源码验证总结

  • HashMap 1.7 :通过Entry类、transfer方法验证链表结构与死循环问题。
  • HashMap 1.8 :通过TreeNode类、putVal方法验证树化逻辑。
  • ConcurrentHashMap 1.7 :通过Segment类与锁机制。
  • ConcurrentHashMap 1.8 :通过Node类、initTable的CAS与synchronized块。
相关推荐
Mr_Xuhhh22 分钟前
LeetCode hot 100(C++版本)(上)
c++·leetcode·哈希算法
云原生指北36 分钟前
命令行四件套:fd-rg-fzf-bat
java·大数据·elasticsearch
人间打气筒(Ada)1 小时前
go实战案例:如何通过 Service Meh 实现熔断和限流
java·开发语言·golang·web·istio·service mesh·熔断限流
主宰者2 小时前
C# CommunityToolkit.Mvvm全局事件
java·前端·c#
计算机学姐3 小时前
基于SpringBoot的咖啡店管理系统【个性化推荐+数据可视化统计+配送信息】
java·vue.js·spring boot·后端·mysql·信息可视化·tomcat
My的梦想已实现3 小时前
关于JAVA Springboot集成支付后打包JAR之后报安全错误的处理
java·spring boot·jar
ooseabiscuit3 小时前
SpringBoot3整合FastJSON2如何配置configureMessageConverters
java
ok_hahaha3 小时前
java从头开始-黑马点评-Redission
java·开发语言
无巧不成书02183 小时前
Java面向对象零基础实战:从Employee类吃透自定义类核心,掌握封装精髓
java·开发语言·java入门·面向对象·自定义类·employee类·java核心技术
小江的记录本3 小时前
【注解】常见 Java 注解系统性知识体系总结(附《全方位对比表》+ 思维导图)
java·前端·spring boot·后端·spring·mybatis·web