Java源码分析——集合容器

集合-容器

Collection单列集合

JAVA-Collection集合源码阅读记录

Set

  • SortedSet
TreeSet

​ 使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。

​ Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的, 自己定义的类必须实现 Comparable接口,并且覆写相应的 compareTo()函数,可以正常使用。

在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序。比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

HashSet
  • 应用场景:去重

    • 与HashMap的关系:

      HashSet底层封装的是HashMap,HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。所有元素添加会放到HashMap的key中,value值使用new Object对象作为value;

      若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写。

  • 特点

    • 数据不能重复;
    • 可以存储null值,数据不能保证插入有序;
LinkedHashSet
  • 继承了HashSet
  • 应用场景:实现了对数据进行去重,并且对集合类数据实现访问有序 ;

List

ArrayList

ArrayList的默认长度是多少?

​ 它的默认初始化长度为10;同时它也是支持动态扩容的,通过这个方法进行动态扩容新增加的容量大小为原容量大小的50%。 底层调用的就是Arrays.copyOf(elementData, newCapacity)。

LinkedList

​ LinkedList:可知该链表是双向链表,即可以从头遍历到尾,也可以从尾遍历到头。同样它也是线程不安全的,在这里最可能的造成的并发原因就是链表成环。

Vector

Queue

Map双列集合

img

Map中的集合核心特性

自动扩容

​ 最小可用原则,容量超过一定阈值便自动进行扩容。

  • 扩容是通过resize方法来实现的。扩容发生在putVal方法的最后,即写入元素之后才会判断是否需要扩容操作,当自增后的size大于之前所计算好的阈值threshold,即执行resize操作。

    图片

  • 通过位运算<<1进行容量扩充,即扩容1倍,同时新的阈值newThr也扩容为老阈值的1倍

    图片

    扩容时,总共存在三个问题

    • 哈希桶数组中某个位置只有1个元素,即不存在哈希冲突时,则直接将该元素copy至新哈希桶数组的对应位置即可。

    • 哈希桶数组中某个位置的节点为树节点时,则执行红黑树的扩容操作。

    • 哈希桶数组中某个位置的节点为普通节点时,则执行链表扩容操作,在JDK1.8中,为了避免之前版本中并发扩容所导致的死链问题,引入了高低位链表辅助进行扩容操作

    图片

    针对扩容时出现的问题的解答

为什么JDK1.7扩容时会产生并发死锁问题,也就是我们常温的为什么hashmap是线程不安全的?

​ 主要原因是:并发,即多线程同时访问HashMap

​ hashmap是线程不安全造成的影响主要有两个方面:1 、高并发下,hashmap会出现扩容的死锁问题;2、数据会丢失,会造成数据的脏读

怎么产生的环形链表死循环问题?

​ JDK1.7 使用的是数组+单链表的数据结构会先进行扩容再插入,执行的是头插法,先将原位置的数据移到后一位,再插入数据到该位置;会出现逆序和环形链表死循环问题

​ JDK1.8 使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率),会先进行插入再进行扩容,执行的是尾插法,直接插到链表尾部/红黑数树,不会出现逆序和环形链表死循环问题

线程不安全是因为:数组确定了就不会修改,想扩容则申请新的数组,把老数据进行迁移,涉及的源码:Entry[] newTable = new Entry[newCapacity]; 当迁移时,单线程迁移没有问题,当有多个线程需要扩容时,都申请了数组,则可能数据迁移不成功,造成JVM内存溢出,并造成大量GC,则当线程迁移时,造成死锁。

​ 在扩容时,在哈希冲突的时候,产生的链表形成环,当有key在成环链表中时,则成死循环。

  • JDK1.7单线程下的扩容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PcxfkX0p-1617325919212)(D:\chencan\img\blog_gif\test_1.gif)]

  • JDK1.7多线程下的扩容:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YvoxTRxc-1617325919213)(D:\chencan\img\blog_gif\test_2.gif)]

​ 在JDK1.8及以后,用的ConcurrentHashMap解决死锁的问题,ConcurrentHashMap是线程安全的对死锁问题进行了改进,采用四组指针,分为高位指针和低位指针,hashcode & 数组容量长度,分成两部分迁移,避免头插链表成环。

ConcurrentHashMap 的线程安全机制:CAS + 锁

​ ConcurrentHashMap保证线程安全的原理思路:

  • T1线程加元素A,CAS判断节点是否为空,为空则用CAS把节点放入

  • T1线程再加元素B,CAS再判断节点是否为空,不为空则加锁后放入,T1线程都可访问

  • T2线程,get(key)时,当没有锁,则直接拿

    扩容原理:ConcurrentHashMap中的put()方法比hashmap()对一个for循环的原因,多个线程需插入值,但CAS算法只能有一个成功,ConcurrentHashMap中加入for循环保证不成功的线程继续放入值,而不失效**(自旋)**

初始化与懒加载

​ 初始化的时候只会设置默认的负载因子,并不会进行其他初始化的操作,在首次使用的时候才会进行初始化。

当new一个新的HashMap的时候,不会立即对哈希数组进行初始化,而是在首次put元素的时候,通过resize()方法进行初始化。

图片

resize()中会设置默认的初始化容量DEFAULT_INITIAL_CAPACITY为16,扩容的阈值为0.75*16 = 12,即哈希桶数组中元素达到12个便进行扩容操作。

最后创建容量为16的Node数组,并赋值给成员变量哈希桶table,即完成了HashMap的初始化操作。

图片

哈希计算

​ 哈希表以哈希命名,足以说明哈希计算在该数据结构中的重要程度。而在实现中,JDK并没有直接使用Object的native方法返回的hashCode作为最终的哈希值,而是进行了二次加工。

<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值