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

Java高频面试之集合-11

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

面试官:详细说说hashmap的put和get操作

HashMap 的 putget 操作是核心功能,其底层通过 数组+链表/红黑树 实现,结合哈希计算与冲突处理完成键值对的存取。以下是详细流程和关键逻辑分析:


一、put 操作流程

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
1. 计算哈希值
  • 扰动函数:通过 key.hashCode() 获取原始哈希值后,将高16位与低16位异或,减少哈希冲突。
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    作用:高位信息参与运算,避免低位重复导致大量哈希冲突。
2. 定位桶(数组索引)
  • 计算数组下标:index = (n - 1) & hashn 为数组长度,必须是2的幂)。
    原理:当 n 是2的幂时,(n-1) & hash 等效于 hash % n,但位运算效率更高。
3. 插入或更新键值对
  • 场景1:桶为空

    • 直接创建新节点(Node)放入数组对应位置。
  • 场景2:桶非空(哈希冲突)

    1. 链表处理

      • 遍历链表,若找到相同 keyhash 相同且 equalstrue),更新值并返回旧值
      • 若未找到,尾插法添加新节点(JDK8之前为头插法)。
      • 链表长度≥8时,转为红黑树TREEIFY_THRESHOLD = 8)。
    2. 红黑树处理

      • 调用 putTreeVal 方法插入节点,保持红黑树平衡。
4. 扩容机制
  • 触发条件:元素数量 > capacity * loadFactor(默认 capacity=16loadFactor=0.75)。
  • 扩容步骤
    1. 新容量 = 旧容量 × 2。
    2. 遍历旧数组,重新计算节点位置:
      • 低位桶:原索引位置。
      • 高位桶:原索引 + 旧容量(利用 hash & oldCap 判断高位是否为1)。
    3. 链表/红黑树拆分后迁移到新数组。

二、get 操作流程

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
1. 计算哈希值
  • put 操作的扰动函数计算哈希值。
2. 定位桶
  • 使用 (n-1) & hash 找到数组下标。
3. 查找键值对
  • 链表查找
    • 遍历链表,比较 hashkeyequals 方法),找到匹配节点返回 value
  • 红黑树查找
    • 调用 getTreeNode 方法,按红黑树特性快速查找(时间复杂度从链表 O(n) 降低到 O(logn))。

三、关键设计细节

1. 哈希冲突优化
  • 链表转红黑树:链表长度≥8时转换(避免哈希攻击导致的性能退化)。
  • 红黑树退化为链表:树节点数≤6时退化为链表(UNTREEIFY_THRESHOLD = 6,避免频繁转换)。
2. 扩容优化
  • 高低位拆分:扩容时无需重新计算所有节点哈希,通过 hash & oldCap 判断高位,直接将链表拆分为低位和高位两部分。
3. 线程安全问题
  • 非线程安全:多线程同时修改可能导致循环链表(JDK7头插法)或数据覆盖。
  • 替代方案:使用 ConcurrentHashMapCollections.synchronizedMap

四、示例流程对比

操作步骤概要时间复杂度(平均)时间复杂度(最坏)
put哈希计算 → 定位桶 → 插入/更新 → 扩容O(1)O(logn)(红黑树)
get哈希计算 → 定位桶 → 遍历链表/树O(1)O(logn)

五、常见面试问题

  1. 为什么负载因子是0.75?

    • 平衡时间和空间成本:负载因子越高,空间利用率高但冲突概率增加;负载因子越低,扩容频繁但查询快。
  2. 为什么链表长度≥8才转红黑树?

    • 根据泊松分布,哈希冲突达到8的概率极低(约千万分之一),此时转为树可显著提升性能。
  3. 头插法改为尾插法的原因?

    • JDK7头插法在多线程扩容时可能导致循环链表,JDK8改为尾插法避免此问题。
  4. 为什么数组长度是2的幂?

    • 保证 (n-1) & hash 均匀分布,且扩容时拆分链表更高效。

在这里插入图片描述


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

相关文章:

  • Day08 实例:3个网站实例探究算法对于密文加密的影响
  • Kafka 消费者组的重平衡
  • 深度学习优化-Gradient Checkpointing
  • ORACLE 19.8版本遭遇ORA-600 [kqrHashTableRemove: X lock].宕机的问题分析
  • CSS:不设定高度的情况,如何让flex下的两个元素的高度一致
  • 历次科技泡沫对人工智能发展的启示与规避措施
  • Python----计算机视觉处理(opencv:图片灰度化)
  • Unity屏幕适配——立项时设置
  • Python使用FastAPI结合Word2vec来向量化200维的语言向量数值
  • 缓存使用的具体场景有哪些?缓存的一致性问题如何解决?缓存使用常见问题有哪些?
  • 蓝思科技冲刺港股上市,双重上市的意欲何为?
  • TI的Doppler-Azimuth架构(TI文档)
  • 山东省新一代信息技术创新应用大赛-计算机网络管理赛项(样题)
  • LSTM方法实践——基于LSTM的汽车销量时序建模与预测分析
  • 华为OD机试-测试用例执行计划(Java 2024 D卷 100分)
  • MIFNet (论文阅读笔记)
  • 【实战篇】MySQL 时间字段的处理
  • java学习笔记1
  • 【eNSP实战】三层交换机使用ACL实现网络安全
  • C++中的单例模式及具体应用示例