当前位置: 首页 > article >正文

Java高频面试之集合-09

hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶

面试官:详细说说HashMap


HashMap 数据结构详解

HashMap 是 Java 中最常用的键值对存储结构,基于 哈希表 实现,核心设计目标是 高效查找与插入。以下是其底层实现原理与关键优化:


一、核心数据结构

在 JDK 1.8 后,HashMap 采用 数组 + 链表 + 红黑树 的复合结构:

  1. 数组(桶数组)

    • 初始容量为 16(可自定义),存储链表的头节点或红黑树的根节点。
    • 数组索引通过 哈希值 & (数组长度 - 1) 计算(等价于取模,但性能更高)。
  2. 链表

    • 哈希冲突时,键值对以链表形式存储在同一桶中(拉链法)。
    • 链表节点为 Node<K,V>,包含 keyvaluehashnext 指针。
  3. 红黑树

    • 当链表长度 ≥8 且数组长度 ≥64 时,链表转换为红黑树(TreeNode),将查询复杂度从 O(n) 优化为 O(log n)
    • 树节点退化为链表的条件:节点数 ≤6(避免频繁转换)。

二、哈希计算与冲突解决
  1. 哈希值计算

    static final int hash(Object key) {
        int h;
        // 高16位异或低16位,增强散列性
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
  2. 哈希冲突处理

    • 拉链法:冲突的键值对存入同一桶的链表或树。
    • 再哈希法:通过扩容重新分布键值对。

三、动态扩容机制
  1. 触发条件
    当元素数量 > 容量 × 负载因子(默认0.75) 时触发扩容。

  2. 扩容流程

    • 创建新数组(容量翻倍,保持 2^n 特性)。
    • 遍历旧数组,重新计算节点位置(利用高位区分新旧索引)。
    • 链表节点按 (e.hash & oldCap) == 0 拆分为低位链表和高位链表。
  3. 扩容优化(JDK 1.8)

    • 单链表拆分:避免多线程下环形链表问题。
    • 红黑树拆分:树节点按哈希值拆分后,若节点数 ≤6,退化为链表。

四、关键操作源码解析
  1. put(K key, V value)

    public V put(K key, V value) {
        // 计算哈希值
        int hash = hash(key);
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        // 初始化或扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        
        // 计算桶索引
        i = (n - 1) & hash;
        p = tab[i];
        
        // 桶为空,直接插入新节点
        if (p == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 桶首节点匹配
            if (p.hash == hash && ((k = p.key) == key || (key != null && 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;
                    }
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 更新值并返回旧值
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 检查扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
  2. get(Object key)

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 检查桶首节点
            if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                // 树节点查找
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // 链表遍历
                do {
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
    

五、性能与优化
  1. 时间复杂度

    操作平均最差
    put/getO(1)O(log n)(树化后)
  2. 调优参数

    • 初始容量:预估元素数量,避免频繁扩容(建议设为 (预期元素数 / 负载因子) + 1)。
    • 负载因子:默认 0.75,权衡空间利用与哈希冲突。
  3. 线程安全替代方案

    • ConcurrentHashMap:分段锁或 CAS 实现高并发读写。
    • Collections.synchronizedMap:包装类,方法级同步。

六、JDK 1.7 vs 1.8 对比
特性JDK 1.7JDK 1.8
数据结构数组 + 链表数组 + 链表 + 红黑树
哈希计算多次扰动(4次位运算+5次异或)1次位运算 + 1次异或
扩容策略头插法(可能产生环形链表)尾插法(避免环形链表)
节点拆分全链表重新哈希高低位链表拆分

七、使用注意事项
  1. 键对象要求
    • 重写 hashCode()equals(),确保哈希分布均匀且相等性正确。
  2. 避免频繁扩容
    • 初始化时指定合理容量。
  3. 线程安全场景
    • 使用 ConcurrentHashMap 替代。

🐮🐎

HashMap 通过 数组 + 链表 + 红黑树 的复合结构,结合 动态扩容哈希优化,实现了高效的键值对存储。理解其底层机制有助于在实际开发中合理调优,规避性能瓶颈与线程安全问题。

在这里插入图片描述


http://www.kler.cn/a/583006.html

相关文章:

  • IDEA修改项目的JDK版本(无缝切换8和11)
  • 前端发布缓存导致白屏解决方案
  • SpringBoot实现文件上传
  • Excel两列和依次相减
  • 【C++入门】变量和基本类型
  • 版本控制器Git(2)
  • [数据结构]排序之希尔排序( 缩小增量排序 )
  • upload-labs-master通关攻略(13~16)
  • 计算机视觉|一文读懂NeRF:为3D场景重建带来新突破
  • CMD批处理一些冷门命令,编写windows脚本常用?
  • java每日精进 3.11 【多租户】
  • ST电机库电流采样 三电阻单ADC
  • Oracle VirtualBox安装CentOS 7
  • FFmpeg入门:最简单的音视频播放器(Plus优化版)
  • dns劫持是什么?常见的劫持类型有哪些?如何预防?
  • XML Schema 实例
  • 深入理解 HTML 链接:网页导航的核心元素
  • 【Deepseek基础篇】--v3基本架构
  • 初学者快速入门Python爬虫 (无废话版)
  • 从零开始的python学习(五)P75+P76+P77+P78+P79+P80