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

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 均匀分布,且扩容时拆分链表更高效。

在这里插入图片描述

相关文章:

  • 【2025】基于springboot+vue+uniapp的厨师预约上门做菜小程序(源码、万字文档、图文修改、调试答疑)
  • 使用Qt创建悬浮窗口
  • NPU的工作原理:神经网络计算的流水线
  • 【开源+代码解读】Search-R1:基于强化学习的检索增强大语言模型框架3小时即可打造个人AI-search
  • Linux动态监控系统
  • C++ std::list超详细指南:基础实践(手搓list)
  • Golang Channel 使用详解、注意事项与死锁分析
  • FANUC机器人几种常用的通讯网络及接口
  • 【零基础入门unity游戏开发——unity3D篇】3D物理系统之 —— 3D刚体组件Rigidbody
  • Docker 部署Spring boot + Vue(若依为例)
  • 探针泄露(WEB)
  • 如何安装旧版本的Pytorch
  • python-leetcode-子数组最大平均数 I
  • matplotlib 保存图片是空的,小坑,记录一下
  • 多种注意力机制(文本->残差->视频)
  • Everything搜索工具下载使用教程(附安装包),everything搜索工具文件快速查找
  • 操作符详解
  • 求递增子序列LIS的两种方法
  • PHP语法基础
  • C++ Primer Plus 编程练习题 第四章 复合类型
  • 住房和城乡建设局部网站/阿里云域名注册
  • 湛江网站建设费用/谷歌搜索引擎免费入口
  • 笔记模板wordpress/鸡西网站seo
  • 企业网站建设维护合同书/国内专业seo公司
  • 做网站用织梦好吗/怎么建立网站卖东西
  • 建设银行校招网站入口/长沙seo行者seo09