集合知识点

集合知识点

集合的层级关系
List接口

|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序(并不是排序)的、可重复的数据。 -->“动态”数组,替换原有的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储,初始化为10个容量,扩容为原来的1.5倍,可以存储null值
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
|----Vector:线程安全 初始化为10个容量,扩容为原来的2倍
对list进行排序

//第一种方式
list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
//第二种方式
Collections.sort(list);
//不能添加null值
ArrayDeque<Integer> arrayDeque1  = new ArrayDeque<>();

解决ArrayList线程不安全

第一种解决方案
List<String> list1 = Collections.synchronizedList(new ArrayList<>());

//利用写时复制技术,多个线程能进行同时读,但写的时候只能一个线程进行写
//原理是先复制一份,然后多个线程读老的集合,然后在复制的一份进行写,最后两个覆盖整合 List<String> list = new CopyOnWriteArrayList<>(); 或者使用vector

Set接口

|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值,底层也是数组+链表,初始容量为16,当如果使用率超过0.75,就会扩大容量为原来的2倍
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。

解决HashSet不安全问题
Set<String> set = new CopyOnWriteArraySet<>();

Map接口(已经是顶层接口)

|----Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序 底层使用红黑树,线程不安全
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value,扩容时是容量翻倍+1 即:capacity * 2+1。
|----ConcurrentHashMap 线程安全
|----Properties:常用来处理配置文件。key和value都是String类型,线程安全

集合类keyvalue
HashMapnullnull
HashTable不能为null不能为null
ConcurrentHashMap不能为null不能为null
TreeMap不能为null可以为null

ps: 关于扩容,其实是会带来性能消耗的,所以最好在一开始就指定大小,这样数组不会进行扩容

HashMap 1.7 和 1.8的对比

1、底层不同
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8),当链表数量大于8并且数组容量大于64时,转换为红黑树,底层的数组是Node[],而非Entry[]
2、造成线程不安全不同
JDK1.7 HashMap线程不安全体现在:死循环(因为采用的是头插法,有可能在并发的时候链表之间造成循环,没有链上的就数据丢失)、数据丢失
JDK1.8 HashMap线程不安全体现在:数据覆盖

由于多线程对HashMap进行put操作**。调用了HashMap#putVal(),**假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

ConcurrentHashMap 1.7 和1.8区别
首先还是底层,和hashmap一样
JDK7中的大致组成,ConcurrentHashMap由Segment数组结构和HashEntry数组组成。 Segment是一种可重入锁,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构。缺点是并发程度是由segment数组来决定的,并发度一旦初始化无法扩容,不过segment里的table可以扩容为2倍,该方法没有考虑并发,因为执行该方法之前已经获取了锁。(其中并发级别控制了Segment的个数,在一个ConcurrentHashMap创建后Segment的个数是不能变的,扩容过程过改变的是每个Segment的大小。),对于整体创建出来的Segment是不能变的,锁的粒度比较大
缺点在于分成很多段时会比较浪费内存空间(不连续,碎片化); 操作map时竞争同一个分段锁的概率非常小时,分段锁反而会造成更新等操作的长时间等待; 当某个段很大时,分段锁的性能会下降。
jdk1.8:
取消了segment数组,直接用table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁
存储数据时采用了数组+ 链表+红黑树的形式。
put操作采用 CAS + synchronized 实现并发插入或更新操作
把数组中的每个元素看成一个桶。可以看到大部分都是CAS操作,加锁的部分是对桶的头节点进行加锁锁粒度很小
为什么不用ReentrantLock而用synchronized ?
减少内存开销:如果使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销,而1.8中只有头节点需要进行同步。
内部优化:synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。
如何初始化initTable
只允许一个线程对表进行初始化,如果不巧有其他线程进来了,那么会让其他线程交出 CPU 等待下次系统调度Thread.yield。会根据sizeCtl的值进行设置,如果没有设置szieCtl的值,那么默认生成的table大小为16,否则,会根据sizeCtl的大小设置table大小。
put操作

判断键值对数组tablei是否为空或为null,为空进行扩容
根据键值key计算hash值得到插入的数组索引i,
判断tablei的首个元素是否和key一样,如果相同直接覆盖value, 相同接入链表 或者红黑树的操作

小知识点
红黑树和平衡二叉树区别

红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

博主暂时写到这,有问题欢迎评论指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值