老掉牙的ArrayList解析它它它又来了

由于本人对多线程板块知识理解的深度不够,所以本文结合了网上面试题的同时略过了ArrayList与多线程结合的题目,或许那才是重点。不过咱们是初学者,先了解就行,不用深究。

写在前头

学完集合的源码,整个人都不好了。以前只知道List、Set、Map三种集合,然后就是对应的增删改查方法,哇,使用集合存数据方便极了,谁还用难用的数组!!!凡是多问几个为什么,你不问,面试官帮你问。好的,所以为了赶在面试官前面,我先来问问自己为什么。
于是就有了你看的这篇博客,当然这仅仅是来自一个初学ArrayList集合的人,知识体系也就不够完善,但是我会加油的,希望对你也有帮助!!!

不过不得不说ArrayList的源码要比HashMap简单很多,只有逻辑代码,很少涉及算法部分(那些二进制算法)。废话不多说,咱们开始吧!!!



建议本文可以配合源码食用

传送门

ArrayList源码剖析



1、List概述

Java 的 List 是非常常用的数据类型。List 是有序的 Collection。Java List 一共三个实现类:

分别是 ArrayList、Vector 和 LinkedList,所以ArrayList也是有序的。下面的图片分别是三种集合的类图,有兴趣的可以了解一下,当然后期我也会继续跟进,毕竟熟透框架 人人有责。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XFwbf6m-1585830586715)(ArrayList%E5%8E%9F%E7%90%86.assets/image-20200402193016573.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vv1CGKvX-1585830586735)(ArrayList%E5%8E%9F%E7%90%86.assets/image-20200402192921408.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYe2il6H-1585830586738)(ArrayList%E5%8E%9F%E7%90%86.assets/image-20200401185749886.png)]




2、ArrayList基本概述

ArrayList是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。




3、常见的ArrayList面试题


3.1、ArrayList是如何进行扩容的

当我们在使用add()方法时,如果我们没有指定对应的容量,系统就会采用默认的10作为集合的容量。(至于为什么是10,我觉得应该是作者综合考虑了很多因素才决定的吧,毕竟扩容挺浪费性能的)当集合的容量超过10以后,集合的容量就开始扩容,每次扩容为之前容量的1.5倍(oldCapacity + (oldCapacity >> 1))。当然,每次调用add()方法的时候,都会去进行容量的判断,继而确定是否扩容。


3.2、为什么ArrayList获取集合的长度是使用size,而不是使用length

在最开始接触集合的时候,老师就跟我们说,获取集合的长度是使用size(),而不是length。调用size()我们不难发现它也只是简单的返回一个size变量。那为什么就不能继续让length继续担任长度呢?

public int size() {
    return size;
}

其实在ArrayList中,有集合容量和集合长度一说。集合的容量就是使用的length(用于扩容),集合长度就是集合中真正的包含的元素使用size。并且我们ArrayList是不能够获取集合容量的,这也就是为什么我们的获取集合的长度是使用的size()而非length。


3.3、ArrayList频繁扩容导致添加性能急剧下降,如何处理?

我们分析源码可得,每次ArrayList集合在添加方法的时候,都会去进行集合容量的判定,继而确定是否需要进行扩容。如果我们能够指定出集合具体的恰当容量,那么系统就不会去扩容,直接使用我们设置的容量。这样就能够避免频繁扩容带来的性能损耗。


3.4、如何复制一个ArrayList到另一个ArrayList中去

1、由于ArrayList类实现了clone()接口,所以我们可以直接使用克隆的方式进行

2、在构造方法中传入集合对象,可以完成复制

3、使用addAll方法传入集合对象,可以完成复制


3.5、 ArrayList和LinkedList区别?

  • ArrayList

    • 基于动态数组的数据结构
    • 对于随机访问的get和set,ArrayList要优于LinkedList
    • 对于随机操作的add和remove,ArrayList并不一定比LinkedList慢 (虽然LinkedList在增删时拥有明显的优势,但是ArrayList底层由于是动态数组,因此并不是每次add和remove的时候都需要创建新数组)
  • LinkedList

    • 基于链表的数据结构
    • 对于顺序操作,LinkedList不一定比ArrayList慢
    • 对于随机操作,LinkedList效率明显较低(因为使用顺序访问只是在获取迭代器的时候进行了一次折半,然而随机操作每次都要去获取)

3.6、ArrayList是线程安全的么?如果不安全,又需要安全怎么办

ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考略使用Collections.synchronizedList(list)返回一个线程安全的ArrayList类。也可以使用ConcurrentLinkedQueue()来保证安全。


3.7、对于ArrayList和LinkedList如何选择使用?

时间复杂度上:

由于ArrayList是动态数组(动态就是可以自动扩容),所以改和查有明显的优势,但是增删由于涉及到扩容效率会相对差些,但并不是每次都会去扩容,所以ArrayList的增删效率差这种说法是有一定问题的。

LinkedList是一个双向链表,会出现在删除和修改上有明显的优势,但查找和修改就有一定的劣势,所以使用LinkedList一定要使用迭代器查找(具体原因在分析源码的时候,我已经说过)。

空间复杂度上:

这个点还真的不是那么好确定的。

ArrayList的容量在10以上时,每次扩容都是上一次的1.5倍,试想如果你现在的集合容量已经很大了,但是为了存最后一个数据,就有可能再次扩容,继而浪费了很多的空间。集合越大,浪费的就越多(当然你也可以通过指定集合大小来避免出现这种问题)。

LinkedList由于需要存储相应的头信息和尾信息,所以也会出现空间的浪费,不过还好,他每次添加的时候浪费的空间比较恒定。




想学习ArrayList源码的:

传送门走起

ArrayList源码剖析

好吧,我承认,这个集合没有什么太多的看点,丝毫没有我学习HashMap集合时候的那种兴奋(可能是因为里面没有算法吧)不过这个集合是通向List集合的大门,只有入了门,学其他两个同类的集合LinkedList、Vector才轻松,只是体系树才能更加茁壮成长呀。

不过源码真的有意思,能学到很多,特别是代码的书写方式

发布了45 篇原创文章 · 获赞 17 · 访问量 3688

猜你喜欢

转载自blog.csdn.net/qq_44377709/article/details/105278511