HashMap、ConcurrentHashMap问题总结

252 阅读2分钟

一、为什么扩容是2的平方?

  1. 通过与元素的hash值进行与操作,能够快速定位到数组下标 相对于取模运算,直接进行与操作能提高计算效率。在CPU中,所有的加减乘除都是通过加法实现的,而与操作时CPU直接支持的。
  2. 扩容时简化计算数组下标的计算量 因为数组每次扩容都是原来的两倍,所以每一个元素在新数组中的位置要么是原来的index,要么index = index + oldCap,假设

二、HashMap的加载因子

  1. 加载因子:0.75
  2. 默认容量:16
  3. 扩容机制2的平方 容量*加载因子 > (size)
  4. map的自定义容量大小,系统内部会向上去2^n次方

三、为什么1.7在扩容的时候回死循环

  1. jdk1.7 链表插入顺序是链表头插入,扩容后的链表与之前是倒序,多线程时会出现链环。
    hashmap死锁的原因

四、JDK1.7和1.8实现的区别

  1. 数据结构:1.7=数组+链表 1.8=数组+(链表/红黑树)
  2. 1.7是插入链表是表头插入(为什么这么设计:最近可能使用概率较大),多线程并发插入、读取会容易导致死循环(造成循环链)
  3. 1.8是链尾插入,先插入然后在扩容。
  4. 转红黑树的条件是,数组>=64,链表节点数>=8,当链表节点数<=6时(红黑树转列表),转红黑树是为了查询快,转回去为什么是6而不是7,是为了防止链表和红黑树频繁的切换。

五、ConcurrentHashMap实现原理

  1. jdk1.7版本
  • initialCapacity:初始总容量,默认16
  • loadFactor:加载因子,默认0.75
  • concurrencyLevel:并发级别,默认16,如果需要修改通过new ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
  • 扩容:指的Segement类内部的hash表扩容
  1. jdk1.8版本
  • initialCapacity:初始总容量,默认16
  • loadFactor:加载因子,默认0.75
  • concurrencyLevel:没有,是根据Hash表的table里面的元素来作为锁的
  • 扩容:ConcurrenHashMap在扩容过程中主要使用sizeCtl和transferIndex这两个属性来协调多线程之间的并发操作,并且在扩容过程中大部分数据依旧可以做到访问不阻塞。
    1. 桶上链表长度达到 8 个或者以上,并且数组长度为 64 以下时只会触发扩容而不会将链表转为红黑树。
    2. sizeCtl值表示的含义
  1. 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
  2. 当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
  3. 当为0时:代表当时的table还没有被初始化
  4. 当为正数时:表示初始化或者下一次进行扩容的大小