集合类

一、HashMap

创建一个HashMap:

1. HashMap对象创建时,实际容量为长度为0的Entry数组
2. put数据时:
  2.1 检测table数组长度是否为空数组
    2.1.1 如果是空数组,根据初始化容量大小capacity计算出大于等于capacity的2的n次方的值
    2.1.2 根据上面计算出来的2的n次方的值,创建Entry数组
  2.2 如果key为null,则直接将数据存储在Entry数组的第一个元素(key为null的值只能存储一个)
  2.3 如果key不为null:
    2.3.1 根据key计算出hash值,然后根据hash值和数组长度减1计算出索引位置(说明:数组长度是2的n次方,2的n次方减1的二进制数全是1,这样有利于hash散列均匀)
    2.3.2 从Entry数组中找到索引位置的数据,遍历链表,如果key相同,则替换value
    2.3.3 如果索引位置的Entry链表上的数据,没有key相同的,则准备将key-value添加到Entry数组中
    2.3.4 添加到Entry数组之前,先检查是否需要进行扩容(扩容为原来的2倍):遍历所有的元素,重新计算索引位置,hash冲突时在链表头插入(不安全,形成环形链表)
    2.3.5 创建新的Entry对象,插入到数组

二、ConcurrentHashMap

1. 创建ConcurrentHashMap:
  1.1 根据concurrencyLevel参数(默认为16)计算Segment数组长度(说明:数组长度是大于concurrencyLevel的最小的一个2的n次方数)
  1.2 根据初始化容量大小(默认是16)计算出Segment内table数组的长度(说明:table数组长度=初始化容量/Segment数组长度,如果计算的结果小于2,则用2作为数组长度)
  1.3 创建Segment数组,并且创建数组的第一个Segment对象
2. put数据:
  2.1 key或value为null,则抛出NullPointerException
  2.2 根据key计算出Segments数组中的索引,如果Segments数组中该索引位置元素为null,则创建Segment对象
    2.2.1 获取Segments数组的第一个Segment对象,以该对象作为原型创建Segment(参考数组长度、加载因子)
    2.2.2 创建好Segment对象后通过CAS方式,插入到Segments数组
  2.3 数据put到Segment对象的table数组(HashEntry[]):
    2.3.1 通过ReentrantLock对Segment对象加锁,如果加锁失败,则循环重试加锁(说明:如果内核数大于1,则最多重试64次,否则重试1次)
    2.3.2 根据key计算出table数组中的位置,获取table数组中的HashEntry对象
    2.3.3 如果HashEntry对象不为null,则将新put的数据加入到HashEntry的链表中
    2.3.4 如果HashEntry对象为null,则新建一个HashEntry对象
    2.3.5 检测容量是否超过0.75,如果超过则进行扩容;否则将HashEntry加入到table数组

3. 扩容:
  3.1 计算出新的HashEntry数组长度(原数组长度*2),并创建新的HashEntry数组;
  3.2 遍历原HashEntry数组元素,计算该元素在新数组中的位置newIndex
    3.2.1 如果HashEntry只有一个元素,则在新数组的newIndex插入该元素
    3.2.2 如果HashEntry有多个元素,则遍历链表,获取到链表中最后一批新index相同的第一个元素,并插入到新数组
    3.2.3 从HashEntry链表头部遍历开始遍历,计算在新数组中的newIndex,创建一个新的HashEntry对象插入到新数组,并将新数组中原存储的对象赋值到新HashEntry的下一个节点(表头插入)

三、CopyOnWriteArrayList

实现原理:底层使用数组实现;当需要修改List元素(add、remove、set)时,先进行加锁,然后将原素组进行复制(Arrays.copyOf)一份进行增、删、改;如果需要从List读取数据,则不加锁直接进行读取。

使用场景:常用在多度、少些写的场景

是否线程安全:不完全(在进行删除操作同时读取集合最后的数据可能会出现数组越界)

原文地址:https://www.cnblogs.com/wuan90/p/14418750.html