Collection(集合)

Collection包含List、Queue、Set三个

List

List中包含了ArrayList、Vector、LinkedList。
==ArrayList:==基于动态数组实现,支持随机访问
Vector: 和ArrayList类似,但是线程安全的。Vector 的增删改查方法都加上了synchronized锁,保证同步的情况下,因为每个方法都要去获得锁,所以性能就会大大下降
==LinkList:==基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素

并发情况下new ArrayList<>不安全
解决方案:1.List list = new Vector<>();
2.List list = Collections.synchronizedList(new ArrayList<>());
3.List list = new CopyOnWriteArrayList<>();(JUC解决方案)
COW:CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWriteArrayList比vector厉害在哪?

  • Vector 的曾删改查方法都加上了synchronized锁,保证同步的情况下,因为每个方法都要去获得锁,所以性能就会大大下降。
  • CopyOnWriteArrayList 方法只是在增删改方法上增加了ReentrantLock锁,但是他的读方法不加锁,所以在读的方面就要比Vector性能要好,

==ArrayList扩容机制 ==
ArrayList是一个数组结构的存储容器,默认情况下数组的长度是10 ,当添加的数据大于10的时候,ArrayList会自动触发扩容机制,
首先创建一个新的数组,这个新数组的长度是原来数组长度的1.5倍,然后使用Arrays。copyOf方法把老数组里面的数据拷贝到新的数组里面,然后在往新数组里面添加数据。

Queue

==ArrayBlockingQueue:==是一个基于数组的阻塞队列,创建时必须要指定数组的长度。线程安全的,内部使用 ReenTrantLock (可重入的互斥锁)来保证线程安全。
==LinkedBlockingQueue:==基于单向链表结构来保存数据的,是无界的。
==LinkedBlockingDeque:==基于双向链表结构来保存数据的,是无界的
==ConcurrentLinkedQueue:==使用CAS的方式来实现线程安全的,性能高于 ReentranLock。
==LinkedList:==同时具有 List 和 Queue 功能,是一个双向链表结构,并发访问不是线程安全的。

Set

==TreeSet ==基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet, 线程不安全。
HashSet:线程不安全

  • HashSet底层实质上是HashMap,可以存放null值,但是只能有一个null
  • HashSet不保证元素是有序的,取决于hash后,再确定索引的结果,即不保证存放元素的顺序和取出顺序一致
  • 不能有重复元素/对象
    底层原理
  • 先获取元素的哈希值(hashcode方法)
  • 对哈希值进行运算,得出一个索引值即为要存放在哈希表中的位置号
  • 如果该位置上没有其他元素,则直接存放,如果该位置上有其他元素,则需要进行equals判断,如果相等,则不再添加,如果不相等,则以链表的方式添加

LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。 线程不安全

Map

TreeMap 要求key必须由同一个类创建的对象,按照key进行排序,底层使用红黑树
== HashMap== 作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value。
== HashTable== 线程是安全的,效率低,不能存放null的key和value。
LinkedHashMap能够记录添加的元素的先后顺序,在HashMap的基础上添加了一堆指针,指向前一个元素和后一个元素

HashMap和HashTable区别:

  • 线程是否安全:HashMap是非线程安全的,HashTable是线程安全的。
  • 效率:因为线程安全问题,HashMap比HashTable效率高一点
  • 对于Null key和Null value的支持:HashMap可以存储null的key和value,但null值只有一个。HashTable不允许有null键和null值。
  • 初始容量大小和每次扩容大小的不同:HashMap默认初始大小为16,每次扩充变为原来的2倍;HashTable默认初始大小为11,每次扩充变为原来的2n+1;
  • 底层数据结构:HashMap的底层数据结构是数组+链表+红黑树,HashTable采用数组+链表。

扩容机制:
HashMap的默认情况下容量是16,当添加某个元素后,数组的总的添加元素数大于了数组长度*0.75 (默认,也可自己设定),数组长度扩容为两倍。(如开始创建HashMap集合后,数组长度为16,临界值为16 * 0.75 = 12,当加入元素后元素个数超过12,数组长度扩容为32,临界值变为24)
HashTable:默认初始大小是11,扩容后会变为原来的2n+1;

HashMap在jdk8中相比较于jdk7底层实现方面有什么不同

  • new HashMap():底层没有创建一个长度为16的数组
  • Jdk 8底层的数组是 Node[]而不是Entry[]
  • 首次调用put方法,底层才会创建长度为16的数组
  • Jdk7底层是:数组+链表 jdk8 :数组+链表+红黑树
  • 形成链表时,七上八下(jdk7:新元素指向旧元素,jdk8:旧元素指向新元素)
  • 当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64,此时此索引位置的数据改为红黑树存储

HashMap负载因子的大小有什么影响?(0.75)
负载因子的大小决定HashMap的数据密度,负载因子越大密度越大,发生碰撞的几率越大,数组中的链表越容易长,造成查询后插入时比较次数增多,性能下降。负载因子越小就越容易出发扩容机制,数据密度小,发生碰撞的几率小,性能高,但是会浪费一定的内存空间

HashMap底层原理
put(k,v):第一步首先将k,v封装到Node对象当中(节点)。第二步它的底层会调用K的hashCode()方法得出hash值。第三步通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true, 那么这个节点的value将会被覆盖。
get(k):先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置.上什么都没有,则返回null。 如果这个位置.上有单向链表,那么它就会拿着参数K和单向链表.上的每一个节点的K进行equals,如果所有equals方 法都返回false,则get方法返回null。 如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

ConcurrentHashMap和HashTable区别

  • 底层数据结构:JDK1.7的ConcurrentHashMap底层采用分段的数组+链表,JDK1.8以后才用数组+链表+红黑树。HashTable底层采用数组+链表。
  • 实现线程安全的方式:在JDK1.7的时候,ConcurrentHashMap对整个数组进行了分段,每把锁只锁容器数组的一部分,1.8以后,摒弃了分段锁,直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用CAS和Sychronized。 HashTable用同一把锁锁住整个数组,使用Sychronized,效率比较低。

ConcurrentHashMap实现原理

  • JDK1.7,ConcurrentHashMap是由Segment数组结构和HashEntry数组结果组成,Segment实现了ReentrantLock(可重入锁),Segment可以看作是HashMap,一个Segment包含一个HashEntry数组,每个hashEntry是一个链表结构的元素。
    当调用ConcurrentHashMap的put方法是,先根据key计算对应的Segment的数组下标,确定好key,value应该插入到哪个Segment对象中,如果Segment为空,则利用自旋锁的方式在该位置上生成一个Segment对象,然后再调用put方法加锁操作;遍历该 HashEntry,如果不为空则判断传入为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。释放锁;

  • JDK1.8:
    1.如果发现key为null或者value为null,则抛出空指针异常
    2JDK8中ConcurrentHashMap为懒惰初始化。若发现tab还没有初始化,则调用initTable()方法对tab进行初始化。(InitTable()方法使用了CAS技术)
    3.通过spread方法处理过的hash值找到Node[] tab的一个Node链表的头结点。
    4.若发现头结点为null,则在CAS技术下创建一个新的结点作为头结点。
    5.不是null的情况下,将头结点用synchronized锁住,遍历链表,若找到相同的key,则更新value;若没有找到,则根据key和value的值新建一个结点追加至链表尾。
    6.调用addCount方法对结点的总数量进行增加计数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值