关于集合,list,set,map 理论基础和面试问题

关于集合,list,set,map 理论基础和面试问题

1.什么是集合?

在java中,通俗的讲,就是包含了同一类对象的容器,这个对象称为集合。

引用一张别人的图

可以看到在日常开发中整体分为三类:list集合(有序可重复)、set集合(无序不可重复)、map(特殊的key-value键值对)集合

2.list集合

​ arrayList:由数组实现,数组的好处就是查询快,增删慢,数据结构基础知识,不在赘述,平时开发使用较多。

linkedList:由链表实现,增删快,查询慢;

vector:数组实现,和arraylist不同的在于当集合在并发情况下作为共享变量时,其实线程不安全的;

copyOnWriteArrayList:此集合也是由数组实现的,线程安全,不同于普通集合的时,此集合时java.juc下类,JUC是专对并发情况下解决集合类不安全的包,每个集合在JUC下都有安全的集合类与之对应,不清楚可以参考b栈遇见狂伸说关于JUC的视频或看相关博客;

另外:既然vector已经是线程安全的了,为什么还要存在copyOnWriteArrayList?

虽然vector也是线程安全的list集合,但是看源码可以发现,vector集合的安全是通过在相关方法加sychronize关键字来实现的,如此便势必导致其执行效率降低

再看copyOnWriteArrayList,看源码可以发现此集合在实现线程安全过程中是通过: 
锁 + 数组拷贝 + volatile关键字实现的, 每次在进行add,delete等操作时会先加锁,然后copy原数组,在进行相关操作,最后将新数组赋值给原来数组。
那么,既然都加锁了,为什么还要在加上数组拷贝和volatile关键字呢?

在我看来:1.主要原因还是在并发过程中为了速度快,而数组拷贝恰恰巧妙的满足了速度快的要求,因为我们知道,数组具有查找快,增删慢的情况,在中间位置增删一个数据的时候,需要找到位置,然后插入值或删除值,而其他后面的值都需要移动据位置,而在再看copyOnWriteArrayList底层实现原理中,对于增删时,会根据新值得位置来走不同的逻辑,加在最后自然不必再说,而加在中间位置的时候,会经过两次复制来完成前一段+新数据位置,然后拼接后一段数据,删除时,尤其是批量删除的时候,会把旧数据不在删除数据集合中的复制到新数组中来完成;可以跟一下源码帮助理解哦!
		2.而为什么要加volatile关键字,volatile是用来修饰数组的,在并发同时处理一个数组的时候,需要让其他线程及时感受到共享变量的变化。而volatile的可见性要在数组地址引用发送变化的时候才会触发,仅仅改变变量值并不会触发;所以三者缺一不可;

3.set集合

hashSet:借助hashmap的key实现,因为hashmap的key是不能重复的,在map进行put新数据的如果key已经存在,则就会报异常;

linkedSet,treeSet,concurrentskipset,copyOnWriteArraySet

set集合在平时使用中,并没有用到很多,一定记得最初始特征,无序可重复,根据此特性来在合适场景使用;

4.map

Hashmap: 依赖hash值来确定数组在map中的位置,平常也是最常用;默认长度16,扩容因为0.75,在jdk1.8的时候采用数组+链表+红黑树实现.

为什么加入红黑树,原因当发生hash冲突时,会将同样的数据在当前hashcode位置,采用链表的形式存储,但是链表的查找效率是比较慢的,为了提高查找的效率,引入了红黑树,当链表向下长度超过8的时候就会转换为红黑树.

而利用红黑树存储时候时候,以根元素为开始,左边元素的值会永远小于右边,所以只需要在向下查找的时候加比较判断当前值即可;

另外,当数据长度达到了数组长度的0.75倍的就会触发revise()发生,发生扩容,已2倍数组的长度进行扩容,扩容时会先创建新集合,然后重新计算每一个值在新集合当中的位置,所以我们在生成过程中,应该尽量避免去让数组发生扩容,在初始化数组时尽可能的给与适当长度;

treemap: 红黑树实现,可以发现,红黑树在集合的出现挺频繁,一般有链表身影的地方,就有红黑树,主要原因还是在当链表过于长的时候,为了优化其查找效率,故此使用了红黑树,将其时间复杂度从 O(N) 提升到 O(logn) (我想应该是log2n(底数为2,指数为n))

关于hashmap,hashtable,concurrentHashMap之间的关系?

三者都是基于hash值实现的,不同点就在于并发过程中的安全性问题.

对于hashmap而言,他是线程不安全,但是同样存在hashtable和concurrenthashmap两种线程安全的map集合,而hashtable是根据sychronize关键字来实现的线程安全,在集合方法上都加了sychronize关键字,保证安全的同时势必也影响了效率;

而concurrenthashmap的实现同样是数组+链表/红黑树的方式在jdk1.7中的时候,通俗的讲,就是根据其结构特性,减小了锁的粒度,锁的时候只会锁数组所在位置的数据,确保同一时间只有一个线程操作当前的hashcode位置;而在1.8中采用了CAS + sychronize; 底层在put数据的时候,会先计算hash值,然后判断当前map是否为null,为null就初始化map,如果不为空,判断当前hash位置是否为null,为null则通过CAS进行写入,CAS写入失败,则自旋,直到成功,如果hash值等于MOVED(-1)则进行扩容,否则用sychronize将map锁起来,完成加入数据;

CAS是基于乐观锁实现的一种compare and swap比较和替换,乐观锁其实也是一种无锁的状态,通过给数据一个version版本来判断如何进行下一步逻辑,核心流程就是CAS过程会有三个数值,内存位置,预期原值和新值,只要当内存位置和预期值一样的时候就会把内存值改为新值;否则自旋,直到成功;为了解决ABA问题和一直自旋,所以加入了版本号version和自旋次数限制;

5.关于java.util.ConcurrentModificationException异常

出现ConcurrentModificationException的原因一般主要有两个

  1. 单线程情况下,使用迭代器进行遍历的过程中,集合进行了remove()操作

    此时注意,在用迭代器进行遍历的时候,如果需要进行remove(),需要使用迭代器的remove();

  2. 在并发环境下,共享变量时不安全的

    很简单,采用JUC下安全的集合类;

6.题外之言

不知道大家在平时用集合的时候会不会使用到stram流式编程.提一句,很方便的哦!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值