Java基础04-集合类

LinkedList
  1. 数据结构

        

  1. LinkedList实现了List接口和Deque接口的双向链表。
  2. LinkedList底层的链表结构是它支持高效的插入和删除操作,具有队列特性。
  3. 线程不安全的,可以通过调用静态方法Collections类中的synchronizedList方法,变成线程安全的。
List list = Collections.synchronizedList(new LinkedList(...));

ArrayList
  1. 数据结构
    1. 底层是动态数组(Object[]),可扩容,其容量能动态增长。
    2. 实现了RandomAccess接口,可支持快速随机访问,可通过元素序号快速获取元素对象
    3. 实现了Cloneable接口,即覆盖了clone(),能被克隆;
    4. 实现了Serializable接口,则支持序列化,能通过序列化去传输。
    5. 内存空间占用角度分析:ArrayList的空间主要体现在会预留一定的容量空间;LinkedList空间主要是每个元素需要占用更多的空间(存储前驱节点和后继节点,和元素数据)
  2. 扩容机制
    1.  注:modCount的作用是迭代器在遍历时,做线程安全检查的。
    2. add()方法,添加一个元素到集合的最后;
    3. ensureCapacityInternal();  判断是否需要扩容;
    4. calculateCapacity();获取当前集合的最小长度;为空,则取默认长度10;
    5. ensureExplicityCapacity();判断当前集合长度+1后长度a,与当前集合对应数组长度b比较;a>b,则调用grow()进行扩容。否则将元素添加进来。modCount++;
    6. grow();扩容;在原集合长度基础上加上,右移一位数值,其相当于扩容到原来的1.5倍;
    7. hugeCapacity();如果扩容后的集合长度,大于最大的数组长度分配值 (Integer.MAX_VALUE - 8),则,大容量扩容到Integer.MAX_VALUE长度。
      1. Arrays.copyOf();底层是调用System.arraycopy()方法。复制原有数组值,到新建的一个扩容后的数组中。
HashMap
  1. JDK1.8之前
    1. 由 Node数组 + 链表组成。数组是HashMap的主体,链表则主要是为了解决哈希冲突而存在的;
    2. HashMap扩容:
      1. 默认情况下数组大小是16,当数组中元素超过数组大小size*loadFactor时,就会进行数组扩容;
      2. 扩容是在原数组基础上,扩大一倍,然后重新计算每个元素在数组中位置,是一个比较耗性能的操作。
      3. put插入值时,插入到链表的头部(会出现环形死循环);
      4. 扩容时,会颠倒链表的顺序
    3. 拉链法解决冲突:创建一个数组链表,数组中元素是一个链表,若遇到哈希冲突,则将冲突的值加到链表中即可。
    4. 多线程下rehash时,会出现死循环主要原因是在于进行扩容时,会将数据从旧的Hash表迁移到新的Hash表中,同时会调转数组中链表的顺序。

                

                      

                  

  1. JDK1.8之后
    1. 数据结构
      1. 由Node数组 + 链表/红黑树组成;线程不安全;键和值都允许为空;
      2. 满足以下2个条件,则会执行链表转红黑树处理,以此来加快搜索速度:
        1. 链表长度大于阈值(默认是8);
        2. HashMap数组长度超过64;
      3. 当数组容量未达到64时,以2倍进行扩容;
      4. put插入元素时,使用尾插法方式,将值插入到链表的尾部;且扩容时,链表顺序不会改变。
    2. 线程不安全的原因:
      1. 多个线程put时,get的值可能不一致,put的操作不是原子性的;
        1. 当多个线程put值时,假如同时去获取同一位置上链表的头节点,当线程A写入新的头节点后,线程B写入新的头节点;这样就导致B的写入操作会覆盖A的写入操作。
      2. 删除键值对时,会删除刚刚修改位置的元素。
ConcurrentHashMap

      java.util.concurrent并发包中线程安全的集合,减少锁的竞争,提高并发率,键和值不能为空

  1. JDK1.7版本
    1. 数据结构
      1. Segment数组+HashEntry数组+链表实现。使用分段锁
      2. 每个Segment上同时只有一个线程可操作,可扩容,冲突会转化为链表,但Segment的个数一旦初始化就不能改变,默认是16个,也就是默认最多支持16个线程并发。
      3. 同一个Segment操作是同步,不同的Segment操作是异步。
    2. put(key,value)方法处理流程:
      1. 计算key的hash值,获取指定位置的Segment
      2. 指定位置为空,则初始化新的Segment;初始化Segment流程如下(CAS):
        1. 检查计算得到的位置Segment是否为null;
        2. 是,则使用Segment[0]的容量和负载因子创建一个HashEntry数组;
        3. 再次检查计算得到的指定位置的Segment是否为null;
        4. 使用创建的HashEntry数组初始化这个Segment;
        5. 自旋判断计算得到的指定位置的Segment是否为null;是则使用CAS(比较并赋值)在这个位置为Segment赋值。
      3. 执行Segment.put()方法插入HashEntry的key.value值:
        1. Segment类继承了ReentrantLock类;
        2. 通过ReentrantLock.tryLock()方法获取锁,获取不到时用scanAndLockForPut方法继续获取;
        3. 计算put的数据要放入的index位置,然后获取这个位置上的HashEntry;
        4. 遍历获取到的HashEntry数组。
          1. 如果这个位置上的HashEntry不存在:
            1. 如果当前容量大于扩容阈值,小于最大容量,进行扩容;
            2. 直接头插法插入
          2. 如果这个位置上HashEntry存在:
            1. 判断链表当前元素key和hash值是否和要put的key和hash值一致,一致则替换值;
            2. 不一致,获取链表下一个节点,直到发现相同进行值替换。否则插入。
              1. 如果当前容量大于扩容阈值,小于最大容量,进行扩容;
              2. 直接链表头插法插入。
        5. 如果要插入的位置之前已存在,替换后返回旧值,否则返回null。
      4. 扩容rehash
        1. 扩容只会扩容到原来的2倍,老数组中的数据移动到新的数组中。待插入的HashEntry对象,会在扩容后使用【链表头插法】插入到指定位置。
        2. 其不会对整个容器进行扩容,而只是对某个Segment 进行扩容;
    3. get(key)方法
      1. 计算key的hash值,确定Segment位置;
      2. 遍历Segment中HashEntry数组中,查找出相同的key的value值;
  2. JDK1.8版本
    1. 数据结构:
      1. 底层是Node数组 + 链表/红黑树实现;
      2. 采用CAS(比较和交换)和synchronized同步锁来保证并发安全,当冲突链表达到一定长度时,链表会转换成红黑树,在冲突小于一定数量时又退回为链表。
      3. synchronized只锁定当前链表或红黑树的首节点,所以只要hash值不冲突,就不会产生并发,提升效率。
    2. put(key,value)方法处理流程:
      1. 计算key的hash值,定位到对用的Node数组中位置;
      2. 判断Node数据是否需初始化,初始化方法 initTable();
        1. sizeCtl变量,决定着当前的初始化状态。
          1. sizeCtl=-1;说明正在初始化,
          2. sizeCtl=-N;说明有N-1个线程正在进行扩容;
          3. 表示Node数组初始化大小,如果Node数组没有初始化;
          4. 或者表示Node数组容量,如果Node数组已初始化。
      3. 通过自旋和CAS操作写入数据:
        1. 首先使用无锁操作CAS插入头节点,如果插入失败,说明已有其他的线程正在插入头节点;再次循环进行操作,如果头节点已经存在,则通过synchronized 获取头节点锁,进行后续操作。
      4. 如果当前位置的hashcode == MOVE == -1,则说明内部正在进行扩容,执行扩容
      5. 判断链表长度是否大于8(TREEIFY_THRESHOLD),且Node数组长度大于64(MIN_TREEIFY_CAPACITY),则将链表结构转化为红黑树结构。
    3. get(key)方法:
      1. 计算key的hash值,并定位Node数组中位置;
      2. 查找到指定位置,如果头节点即为所要找的值,则返回对应的value;
      3. 如果头节点hash值小于0,说明正在扩容或为红黑树,则循环查找;
      4. 如果是链表,遍历查找。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值