Java集合详解

1、List实现方式

底层数据结构实现方式线程安全性初始容量优点缺点备注
数组ArrayList增加或减少元素时,需要考虑扩容;直接修改源数据。非线程安全初始容量:1.6和之前为10。jdk1.7+默认为0,增加一个元素后为10
扩容增量:1.6和之前为原容量的 1.5倍+1一次扩容后是容量为16。1.7和之后是1.5倍也就是15
按索引查找元素快新增减少元素需要考虑数组容量问题,数组的扩容与缩容有一定的性能损耗java.util
链表LinkedList双端链表;增加元素时添加到尾节点;删除元素时,删除首节点。非线程安全-新增、删除元素效率高查找元素稍慢java.util
数组CopyOnWriteArrayList增加或减少元素时,都会创建一个新的数组,并且加锁线程安全-按索引查找元素快;不需要考虑数组的扩容与缩容增加或减少元素的操作都会创建一个新数组,并发环境下内存消耗需要考虑;java.util.concurrent
listSynchronizedList包装list,对每一个list的方法,都用synchronized修饰(静态代理的形式)线程安全-优点与其底层的list一样缺点与其底层的list一样;同时,因为每一个方法都加锁,所以性能稍差Collections内部类

2、Set实现方式

数据结构线程安全性是否有序初始容量容量因子是否可以为null
HashSet在 JDK 1.7 之前,哈希表,它是一个以链表为元素的数组。
在 JDK 1.8 之后,链表数组 + 二叉树,当链表长度大于 8 时,就将量表换成二叉树。
数组+链表(底层就是HashMap)
非线程安全无序160.75可以有一个Null
TreeSet红黑树(底层就是TreeMap)非线程安全有序(自然排序和定制排序)--不能为Null
LinkedHashSet链表和哈希表非线程安全以插入顺序存储--可以有一个Null
img img img

3、Map

1、实现方式

并发性线程安全有序性底层数据结构初始容量负载因子实例化方式一致性k/v是否可为null
HashMap不支持不安全无序数组+链表/红黑树(1.8才有)。链表转红黑树的阈值是8。红黑树转链表是6。16一次扩容后是容量为32(2的5次方)0.75懒加载-k/v可为null
LinkedHashMap不支持不安全有序(插入序或者访问序)数组+单向链表+双向链表----k/v可为null
TreeMap不支持不安全自然序(左小右大)红黑树----仅v能为null
ThreadLocalMap不支持不是绝对的线程安全无序数组160.75懒加载-仅v能为null
HashTable支持安全synchronized实现(锁住全部数据)无序数组加链表11(2n+1,下一次为23)0.75初始化创建强一致性均不能为null
ConcurrentHashMap(1.7)支持安全无序分段锁+数组+链表160.75懒加载强一致性均不能为null
ConcurrentHashMap(1.8)支持安全无序数组+链表/红黑树+锁分段(通过Segment数组+分段锁实现线程安全:每一把锁用于锁容器其中一部分数据)160.75懒加载强一致性均不能为null
ConcurrentSkipListMap支持安全自然序(左小右大)跳跃表---强一致性均不能为null

2、HashMap扩容过程

阿里面试官最喜欢问的21个HashMap面试题

区别JDK1.7JDK1.8
JDK1.7数组 + 链表数组 + 链表 + 红黑树
存储结构初始化方式单独函数:inflateTable()直接集成到了扩容函数==resize()==中
hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 < 8 :存放单链表;冲突 & 链表长度 > 8 & 数组长度 < 64 : 扩容;冲突 & 链表长度 > 8 & 数组长度 > 64:树化并存放红黑树
插入数据方式头插法(先将原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)
  • 调用put方法时:根据key的hashCode,计算出将key放入数组的index下标,index= (数组长度 - 1) & hashCode

image-20210902200228936

3、为什么ConcurrentHashMap是线程安全的?

  1. 为什么ConcurrentHashMap是线程安全的
    JDK1.7中,ConcurrentHashMap使用的锁分段技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

  2. 那说说JDK1.7中Segment的原理
    刚刚说的一段一段就是指Segment,它继承了ReentrantLock,具备锁和释放锁的功能。ConcurrentHashMap只有16个Segment,并且不会扩容,最多可以支持16个线程并发写。

  3. JDK1.8的ConcurrentHashMap怎么实现线程安全的
    JDK1.8放弃了锁分段的做法,采用CAS和synchronized方式处理并发。以put操作为例,CAS方式确定key的数组下标,synchronized保证链表节点的同步效果。

  4. JDK1.8的做法有什么好处呢

    • 减少内存开销
      假设使用可重入锁,那么每个节点都需要继承AQS,但并不是每个节点都需要同步支持,只有链表的头节点(红黑树的根节点)需要同步,这无疑消耗巨大内存。
    • 获得JVM的支持
      可重入锁毕竟是API级别的,后续的性能优化空间很小。synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。
  5. HashTable也是线程安全的,为什么不推荐使用HashTable呢
    HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为多个线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

4、Map性能比较

getputremove
HashMap>=O(1)O(1)~O(log n)O(1)~O(log n)
LinkedHashMap>=O(1)O(1)~O(log n)O(1)~O(log n)
TreeMapO(log n)O(log n)O(log n)
ThreadLocalMapO(1)~O(n)O(1)~O(n)O(1)~O(n)
HashTable>=O(1)O(1)~O(n)O(1)~O(n)
ConcurrentHashMap(1.7)>=O(1)O(1)~O(log n)O(1)~O(log n)
ConcurrentHashMap(1.8)>=O(1)O(1)~O(log n)O(1)~O(log n)
ConcurrentSkipListMapO(log n)O(log n)O(log n)

集合框图

20200314120247303

1010726-20170621004734695-988542448

a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小凯77

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值