ArrayList源码解析

一、前言

ArrayList我们在实际开发中是一个很常用的数据结构,可以称它为动态数组。我们平常用的这个形式的:int[] a = new int[size]称为普通数组,在new时候需要指定其大小(size),不能够动态的增加其大小,那么接下来我们来看看ArrayList为什么称之为动态数组。

二、正文

1.ArrayList有用过嘛?它是一个什么东西?用来干什么的?

有用过,ArrayList就是数组列表,主要用来装载数据,当我们装载的是基本类型的数据int、long、boolean、short、byte…的时候,我们只能存储它们对应的包装类型,它的主要底层实现是数组Object[] elementData。

与它类似的是LinkedList,和LinkedList相比,它的查找和访问元素的速度较快,但是新增和删除的速度较慢。

小结:ArrayList底层是用数组实现的存储。

特点:查询效率高,增删效率低,线程不安全,使用频率很高。

2.它的底层实现是数组,但是数组的大小是定长的,如果我们不断的往里面添加数据,不会有问题嘛?

ArrayList可以通过构造方法在初始化的时候指定底层数组的大小。

通过无参的构造方法ArrayList()初始化,则赋值底层数组Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以数组容量为0,只有真正对数据进行add时,才分配默认的DEFAULT_CAPACITY = 10的初始容量。

大家可以分别看下他的无参构造器和有参构造器,无参就是默认大小,有参会进行判断

在这里插入图片描述

3.数组的长度是有限制的,而ArrayList是可以存放任意数量对象,长度不受限制,那是如何实现的?

其实实现方式很简单,就是通过数组扩容方式实现的。

就比如我们现在有一个长度为10的数组,现在我们要新增一个元素,发现已经满了,那ArrayList应该如何处理呢?

在这里插入图片描述

第一步他会重新定义一个长度为10+10/2的数组,也就是长度为15的数组。

在这里插入图片描述

然后把原数组的数据,原封不动的复制到新的数组中,这个时候在把指向原数组的地址换到新数组,ArrayList这样就完成了扩容的操作。

在这里插入图片描述

4.ArrayList默认的数组大小是多少?为什么?

默认的数组大小是10,通过源码可以看到:

在这里插入图片描述

至于为什么,可能作者认为10是最常用最有效的把。

5.它增删很慢,那增删的时候ArrayList具体做了什么操作?它为什么慢?

它有指定index新增,也有直接新增,在这之前它会有一步校验长度的判断ensureCapacityInternal,就是如果长度不够,需要进行扩容。

在这里插入图片描述

在扩容的时候,采用了位运算,右移一位,也就是除2,用位运算效率更高。

在这里插入图片描述

指定位置新增的时候,在校验之后的操作很简单,就是数组copy,大家可以看下代码:

在这里插入图片描述

这里通过画图解释解释下arraycopy的作用,这样会更加清楚些:

比如有下面这样一个数组,我需要在index 5的位置新增一个元素A:

在这里插入图片描述

那从代码中可以看出,它复制了一个数组,是从index 5的位置开始,然后把它放在index 5+1的位置。

在这里插入图片描述

给我们要新增的元素腾出位置,然后在index的位置放入元素A就完成了新增的操作

在这里插入图片描述

至于为啥效率低,我就不用说了吧,我只是在一个这么小的List里面操作,要是在一个几百几千几万大小的List新增一个元素,那就需要后面所有的元素都复制,然后如果在涉及到扩容啥的那就更慢了。

6.问一个真实的场景,ArrayList(int initialCapacity)会不会初始化大小?

不会初始化大小!

而且将构造函数与initialCapacity结合使用,然后使用set()会抛出异常,尽管数组已创建,但是大小设置不正确。

使用sureCapacity()也不起作用,因为它基于elementData数组而不是大小。

进行此工作的唯一方法是在使用构造函数后,根据需要使用add()多次。

大家可能有点懵,我直接操作一下代码,大家会发现我们虽然对ArrayList设置了初始大小,但是我们打印list大小还是0,我们操作下标set值的时候也报错,数组下标越界。代码如下:

在这里插入图片描述

7.ArrayList插入删除一定慢嘛?

取决于你删除的元素离数组末端有多远,ArrayList拿来作为堆栈还是挺合适的,push和pop操作完全不涉及数据移动操作。

8.ArrayList删除操作是怎么实现的?

删除其实和新增是一样的,不过叫是叫删除,但是在代码中我们发现,它还是在copy一个数组。

通过下面源码可以看出:

在这里插入图片描述

在打个比方,我们要删除下面数组中index 5位置的元素:

在这里插入图片描述

那代码他复制一个index 5+1开始到数组的最后,然后把它放到index开始位置。

在这里插入图片描述

index5位置元素就成功被“删除”了,其实就是被覆盖了,给了你删除的感觉。

同理它的效率也地,因为数组如果很大的话,一样需要复制和移动的位置就大了。

9.ArrayList是线程安全的嘛?

当然不是,线程安全版本的数组容器是Vector。

Vector的实现很简单,就是把所有的方法统统加上Synchronized就完事了。

你也可以不用Vector,用Collections.synchronizedList把一个普通ArrayList包装成一个线程安全版本的数组容器也可以,原理同Vector是一样的,就是把所有的方法套上一层Synchronized。

10.ArrayList适合做队列嘛?

队列一般是FIFO(先进先出)的,如果用ArrayList做队列,就需要在数组队尾追加元素,在对头删除元素,反过来也可以。

但是无论如何总会有一个操作涉及到数组的数据迁移,这个是比较耗性能的。

11.数组适合做队列嘛?

数组是非常适合的。

比如ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。

12.ArrayList的遍历和LinkedList遍历性能比较如何?

论遍历ArrayList要比LinkedList快的多,ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅度降低读取内存的性能开销。

总结

ArrayList数据结构是我们平常开发中比较常用的数据结构了,本文首先介绍了其底层的数据结构和其特点,并且和LinkedList做了对比,之后介绍了其增删底层的逻辑。

最后引用我很佩服的一个人经常说的话:你知道的越多,你不知道的越多!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值