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块。
相关推荐
考虑考虑14 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613514 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊15 小时前
Java学习第22天 - 云原生与容器化
java
渣哥17 小时前
原来 Java 里线程安全集合有这么多种
java
间彧17 小时前
Spring Boot集成Spring Security完整指南
java
间彧17 小时前
Spring Secutiy基本原理及工作流程
java
Java水解18 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆20 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学21 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole21 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端