05 ArrayList LinkedList CopyOnWriteArrayList

1. ArrayList
  • 基于数组实现,实现了 List 接口。(允许传入空值和重复元素)
  • 当添加元素数量大于数组容量时,通过1.5倍的扩容机制进行扩容。
  • 实现了 RandomAccess 接口,表明它具有随机访问的能力,RandomAccess 接口里其实什么内容都没有,它只是一个标识,ArrayList 能实现随机访问真正依靠的是基于数组实现的底层结构。
  • 新增和删除操作会造成数组元素的移动,时间复杂度为 O(n)。由于支持随机访问,所以get和set操作的效率很高,时间复杂度为 O(1)。所以 ArrayList 适用于查询较多,而插入和删除较少的场景。
  • 线程不安全。多线程环境下可以选择用 CopyOnWriteArrayList 。
  • (实现了 java.io.Serializable 接口,意味着 ArrayList 支持序列化)
  • (对ArrayList 进行遍历时,不推荐使用 foreach 循环遍历,因为 foreach 最终会被转换成迭代器遍历的形式,效率不如 for(int i=0;;)的遍历方式)
2. ArrayList如何扩容?负载因子是1

当new一个ArrayList时,如果不指定容量,那么初始容量为0,当对数组添加元素时,会默认分配10个容量。当容量用完后,会按照1.5倍扩容,扩容时会新建一个数组,容量是原本的1.5倍,将旧的数组中的元素添加到新的数组中,再将旧数组的引用指向新数组,就完成了扩容。

3. LinkedList
  • 基于双向链表实现,不仅实现了 List 接口,还实现了 Deque 接口。
  • 不支持随机访问,插入和删除操作的时间复杂度为 O(1),查找操作的时间复杂度为 O(n)。(优先使用 foreach 来遍历)
  • ArrayList 需要在尾部预留空间,LinkedList 不用。
  • LinkedList 用在查询较少、插入删除较多的场景。
  • LinkedList 也是线程不安全的。
  • (多线程环境下可以选择用 ConcurrentLinkedQueue,非阻塞线程安全的队列,基于 CAS 实现的) 。
  • (实现了 java.io.Serializable 接口,所以支持序列化)
4. ArrayList和LinkedList的区别?实现原理
  • ArrayList底层是动态数组;LinkedList底层是一个双向链表
  • ArrayList可以随机访问元素;LinkedList不能随机访问元素,只能从头到尾依次访问。
  • ArrayList在进行添加删除元素操作时,需要移动元素,效率很低;LinkedList在进行删除和添加元素时,只需要更改指针,效率较高。
5. ArrayList和LinkList是线程安全的吗,有线程安全的ArrayList吗?

都不是,Vector是线程安全的,内部的方法使用了synchronized关键字来同步。
concurrent 并发包下的CopyOnWriteArrayList是线程安全的,底层数组使用volatile修饰,修改数组时加ReentrantLock锁,但是只能保证最终数据一致性,不能保证数据的实时一致性。

6. CopyOnWriteArrayList是如何保证线程安全的?
  • 原理:CopyOnWriteArrayList底层有一个array数组,是被volatile修饰的,可以在多个线程之间保持可见性,读操作没有加锁,array可以被多个线程同时读。写操作的话,在修改元素之前使用ReentrantLock进行加锁操作,然后将当前数组进行Copy,复制出一个新的数组,然后在新的数组里修改元素,修改完元素之后,再将原数组的引用指向新数组,最后释放锁。

  • 过程:CopyOnWriteArrayList刚创建时,默认的大小为0,当向其插入一个元素时,将原数组复制到一个比原数组大1的新数组中,然后直接将插入的元素放置到新数组末尾,再修改array引用到新数组就可以,原来的数组就会被垃圾收集器回收。

  • 为什么修改元素时要复制原数组去操作

  1. 复制原数组再去操作,写操作在一个复制的数组上进行,读操作还是在原始数组上进行,就可以实现读写分离,互不影响。
  2. array属性是被volatile修饰的,将array指向一个新数组,其他线程就可以意识到这个修改,如果直接在原数组上进行修改的话,其他线程意识不到这个修改,读取的可能还是线程缓存中的数据。
  • 初始化为什么要设置数组大小为0呢?
    这是因为CopyOnWriteArrayList每次进行写操作时,都会复制原数组到新的数组中,实际占用的空间是原来的两倍,这样的空间开销,导致了CopyOnWriteArrayList不能像ArrayList那样初始化大小为10,不然太浪费空间了,而且CopyOnWriteArrayList主要用于读多写少的地方。

  • 缺点:写操作时内存占用变为两倍;进行读操作时,写操作的数据还未同步,会导致不能读取实时性的数据,只能保证数据的最终一致性,所以不适合实时性要求很高的场景。

扩充知识点
  • ArrayList 扩充知识点: 如果准备添加非常多的数据,那么通过for循环一步步添加时,每当碰到容量不足的情况就会进行一次1.5倍扩容,然后重新分配增量。为了重新分配的次数,可以先调用 ensureCapacity(要添加的数据大小) 方法,来让 ArrayList 进行对应的扩容,然后在添加数据,这样可以减少增量重新分配的次数。
  • Java 提供的线程安全的队列(Queue) 有:
    • 非阻塞型的 ConcurrentLinkedQueue :采用 CAS 实现。
    • 阻塞型的 BlockingQueue 。阻塞队列
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值