欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Java高频面试之集合-09

Java高频面试之集合-09

2025/3/12 22:51:13 来源:https://blog.csdn.net/2401_87189717/article/details/146191404  浏览:    关键词: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 通过 数组 + 链表 + 红黑树 的复合结构,结合 动态扩容哈希优化,实现了高效的键值对存储。理解其底层机制有助于在实际开发中合理调优,规避性能瓶颈与线程安全问题。

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com