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

HashMap高频面试知识点

  • HashMap
    • HashMap是基于hash表的一种数据结构,用于存放键值对,核心就是把hash值映射到数组的索引位,通过数组+链表(JDK1.8开始通过数组+链表+红黑树)解决Hash冲突。
      • 因为当hash冲突较多时,链表中元素增加,查询插入等操作的时间复杂度从O(1)——>O(n)
      • 而二叉树的时间复杂度O(log n)
    • 数组下标=数组长度-1&hash(key)不同的key可能会产生相同的hash值,从而导致hash冲突,jdk1.7之前链表采用头插法,但是多线程环境下可能导致环路从而引发死循环。Jdk1.8之后采用尾插法
    • HashMap初始容量16,负载因子0.75,那么阈值就是16*0.75=12,超过12就会触发2倍扩容操作
    • 扩容时,每个元素存储位置将根据新的数组容量重新计算hash值,并移动到新的数组中,这个操作加rehashing;
      • Jdk1.7之前,就是存粹的把所有元素重新hash,然后一个一个搬过去
      • Jdk1.8之后,旧数组长度16即010000,新数组长度32即100000
      • 而数组下标=(数组长度-1)&hash(key),16-1为001111,32-1为011111
      • 所以重点就在于key的hash值从右往左第五位是否为1,如果为1那么久需要搬迁,新数组下标=原下标+旧数组长度16。
      • 如果是0的话,怎么与都是0,所以新数组高位按位与操作不受影响(注意:与操作有0为0,全1为1),那就说明在原位置,不需要迁移。减少了一部分hash计算的开销
    • 从jdk1.8开始,为了优化hash冲突时的查询性能,
      • 当链表的数量大于等于8并且数组的大小大于等于64,链表就会转换成红黑树。
      • 当数组长度小于64,则选择扩容。
        • 当数组容量较小,过早的转换成红黑树,可能很快又会触发数组扩容,导致没必要过早树化的开销。
        • 另外红黑树比链表更占内存。
      • 树的元素低于6的时候,树就会转换成链表。
    • 常见的处理hash冲突的办法:拉链法,开放寻址法,再哈希法
  • HashMap VS Hashtable
    • 线程安全
      • HashMap 线程不安全,多线程环境下会产生数据一致性问题。
      • Hashtable 线程安全,内部的方法采用了synchronized,保证了多线程并发的安全性。
    • 性能差异
      • HashMap 由于没有线程同步的开销,所以性能优于Hashtable。
      • Hashtable 由于方法的锁机制,性能不如HashMap。
    • 空值null
      • HashMap 允许一个null键,多个null值。
      • Hashtable 键值都不允许null,会报空指针异常。
  • ConcurrentHashMap
    • ConcurrentHashMap是Hashtable的替代方案,读操作的无锁化写操作的分段锁机制提高了并发的性能,避免了全局锁的瓶颈。性能优于Hashtable。
    • JDK1.7 ConcurrentHashMap
      • 采用的是分段锁,每个Segment是独立的。默认16个,所以最多支持16个并发。
      • 采用数组+链表
      • 原理 :
        • 先通过key的hash判断应该在哪个Segment的数据下标,然后对这个Segment加锁(Segment继承ReentrantLock,自带加锁的能力)
        • 然后再次通过key的hash得到Segment里HashEntry的数组下标,接下去的步骤同HashMap
      • 扩容:
        • 扩容时针对单个Segment进行扩容的,不会影响其他的Segment,每个Segment都有自己的负载因子。因此它并不是针对整个ConcurrentHashMap扩容的。
      • Size大小:先尝试不加锁三次计算sum值,如果三次值相同,就直接返回;如果不同就对每个Segment进行加锁计算
      • 缺陷:Segment数组一旦初始化了之后,就不会扩容,导致对并发度控制过于死板(只有HashEntry才会扩容
      • 结构如下

  • JDK1.8 ConcurrentHashMap
    • 移除了Segement的概念,锁的粒度更加细化,通过CAS进行写入操作,只有在更新链表和红黑树的节点时才会用到synchronized,并且只锁链表或树的头节点(相当于Node数组的每个节点都能上一把锁,并发度更高了)。
    • 数组+链表+红黑树
    • 原理:
      • 先计算key的hash后得到数组下标,如果数组下标没有Node,就通过CAS塞入新的Node。
      • 如果当前数组下标存在Node,那么则先通过Synchronized对这个Node加锁。
      • 然后再判断key是否相等,key相同就替换value, 反之就Hash冲突(操作同HashMap)
    • 扩容:
      • Jdk1.8 取消了Segment,因此整个ConcurrentHashMap数组都会被扩容。通过CAS操作确保线程的安全,避免锁住整个数组,并且扩容时多个线程共同参与,逐步迁移就数据到新数组中。
      • 在putVal()的时候,如果无限循环的内部正在扩容,就调用helpTransfer()帮忙一起扩容,扩容完毕后还会向新的数组中插入元素的。

  • Size大小:jdk1.8中,搞了一个数组CounterCell ,每次put和remove的时候,先通过CAS修改baseCount的值,如果CAS失败,则说明存在线程竞争,就要通过hash到对应的CounterCell数组的下标下维护对应的数量;最终size=baseCount+所有的CounterCell
  • ConcurrentHashMap volicate关键字保证了可见性,使得get的时候不用加锁也能线程安全。
  • ConcurrentHashMap key 和value都不支持null。

http://www.kler.cn/news/311825.html

相关文章:

  • 【Ubuntu】ubuntu如何使用ufw(Uncomplicated Firewall)管理防火墙?一文带你学会!
  • Ubuntu-24.04中Docker-Desktop无法启动
  • 怎么操作使http变成https访问?
  • 力扣 中等 2300.咒语和药水的成功对数
  • OpenAI最新发布的o1-preview模型,和GPT-4o到底哪个更强?
  • 驱动---动态模块编译
  • win11开始按钮点不开(已解答)
  • sql中拼接操作
  • 从“治理”到“智理”,看大模型如何赋能智慧政务
  • Linux 信号的产生
  • Windows本地pycharm使用远程服务器conda虚拟环境
  • 【Android】Handler用法及原理解析
  • Rust编程的作用域与所有权
  • 面向开发者的LLM入门教程(学习笔记02):提示原则
  • 探索AI大模型:从入门到精通的学习路径
  • spring cxf 常用注解
  • 大数据时代的等保测评:数据安全与隐私保护
  • [数据集][目标检测]智慧养殖场肉鸡目标检测数据集VOC+YOLO格式3548张1类别
  • leetcode75. 颜色分类
  • 【HTML】入门教程
  • 【SpinalHDL】Scala编程之伴生对象
  • Vue 项目中引入 Axios 详解
  • 【论文阅读笔记】YOLOv10: Real-Time End-to-End Object Detection
  • 【高级编程】网络编程 基于 TCPUDP 协议的 Socket 编程
  • Remix 学习 - @remix-run/react 中的主要组件
  • 网络-内核是如何与用户进程交互
  • MySQL从入门到精通
  • MyBatis 数据处理:主键获取、批量删除与动态表名
  • Linux 磁盘清理重新格式化挂载脚本及问题解决
  • flink doris批量sink