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块。
相关推荐
苦逼的老王2 分钟前
java之uniapp实现门店地图
java·开发语言·uni-app
austin流川枫3 分钟前
如何基于缓存设计实现一个商品最近搜索记录功能
java·redis
无际单片机编程27 分钟前
单片机OTA升级中Bootloader怎么判断APP有没有问题?
java·stm32·单片机·嵌入式硬件·嵌入式
A boy CDEF girl35 分钟前
【JavaEE】多线程进阶(2)
java·java-ee
我真的不会C35 分钟前
Mysql表的复合查询
java·数据库·mysql
Easonmax37 分钟前
【javaEE】多线程(基础)
java·java-ee
ChaHae-In44 分钟前
JavaEE_多线程(二)
java·java-ee
时雨h1 小时前
Spring MVC 详细分层和微服务
java·数据结构·数据库·sql
百香果果ccc1 小时前
Maven的依赖管理
java·数据库·maven