数组和ArrayList源码分析
本文需要你具备一些简单的Java知识,主要是在ArrayList
这个集合框架有一定的使用经验,如果你没有太多经验,可以查看该网站之前有关于集合框架的文章中ArrayList
的内容,本文JDK版本为1.8
数组
什么是数组
用非常官方的话来说:
数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。 这些有序排列的同类数据元素的集合称为数组。
看不懂也没关系
简单来说:想象一个容器,一个空间,有很多小柜子,每个小柜子上面都有一个序号(注意序号是有序的,即类似1,2,3,4这样),其中每个小柜子里面都存放着相同的东西。想象游泳馆的存放物品的小柜子或者丰巢快递柜(不过快递柜标号没有显示出来)
数组在内存中的储存方式
根据上面的描述,你完全可以把计算机的内存想象成游泳馆的小柜子,计算机中的信息都储存在一个一个小的柜子里,无非是把序号换成了地址而已。然后在该地址存放数据。
当我们新建一个数组的时候,会从计算机中拿出顺序的几个空间大小的柜子当作一个整体。接下来就可以填充数据进入数组中了,把数据放入与之对应的序号(地址)中。
然后0号代表第一位元素,1代表第二位,以此类推。
为啥用0代表第1位而不是用1呢?那是因为我们在计算的时候,假如第1位用1表示的话,该位置的地址=初始地址+1-1。假如使用0的话可以少进行一次减法运算,计算机的运算资源十分宝贵,某一方面也是这样考虑的。
数组的优点和缺点
优点:支持快速访问数据
因为数据与对应的序号一一对应,所以你把拿取数据时,通过相应的序号就可以。哪怕不知道地址,你知道在第几位,也可以通过首位地址+第几位来快速拿到数据。
缺点:写入和删除数据时比较麻烦
我们写入数据的时候要把数据放入柜子,如果序号3这个柜子有东西 了,那我们把数据放入3号柜的话就必须把3号柜清空了。那假如我们就一定要在3号柜子放入数据,而不影响之前的数据那该怎么办呢?
把3号柜的所有物品都统一向后面移动1位。当然如果追加数据是在末尾的话就不用移动元素了,删除也是一样的。
这样操作就会造成只要不是在末尾操作数组,数组每次更改都会影响该序号后面的数据(统一前移或者后移)
ArrayList
源码分析
在Java中,数组是在声明时已经写定了大小,但是不可变的数组实际上对于编程来说是很麻烦的事情,因为我们不知道会在该数组中存入多少数据。于是引入了ArrayList
这个集合框架类,当然不只是它做到这个功能,而是该类我们比较常用而已。
ArrayList
运行总览
ArrayList
和核心操作是通过数组复制来达到为我们实现可变化的数组这样的操作。
运行流程如下
类初始化(创建空数组)-加入第1个元素时比较该数组能否承受这个数据-如果不能就扩容以后加入
所以该类的重点在于类初始化,扩容方法,加入数据的方法。
ArrayList
方法源码具体分析
类的基础属性
在关注构造器之前,我们先关注类中的几个属性和该类继承的接口
RandomAccess
这个接口是一个标志接口,表明实现这个接口的List集合是支持快速随机访问的。在该类中,我们可以通过get
方法快速获取元素对象。
Cloneable
实现了克隆接口,覆盖了函数clone()
,能被克隆
java.io.Serializable
该类支持序列化
1 |
|
DEFAULT_CAPACITY
是默认的扩容系数,在第1此添加数据的时候会扩容,这个在后面添加方法时会具体讲到,后面两个空数组效果都差不多,不过系统会根据是不是第二个数组来判断是不是第一次添加数据。elementData
你可以理解为存放着这个ArrayList
的数据,size
很好理解,就是这个ArrayList
的长度,就像数组中的length
。
类的初始化
然后我们关注该类的构造器
1 |
|
这三个构造器都挺常用的,可能你在想第二个构造器明明是返回一个空的,怎么注释里面写得是构造一个初始容量为10的数组,但是赋值的时候,那个常量明明是空数组。这个问题后面会解答。
第一个构造器是通过你手动传值来创建一个具有初始容量的数组。第三个构造器是通过其他集合类的数组来创建数据。
增加数据
来看看非常关键的add
方法,虽然只有短短几行代码。
1 |
|
非常朴实的代码,不过其中的重点在第1行,这是ArrayList
扩容的方法。这个方法我们放在下面讲。总之这个方法就是扩容以后,把最后一个位置装上数据,返回true。
扩容方法
扩容方法是ArrayList
的重点部分,这里将解释为什么我创建一个空的ArrayList
,系统注释会说为创建一个长度为10的List
1 |
|
总之,扩容是从上至下的。每次add方法都需要调用校验方法,我们先假定这是第一次加入数据。首先判断是否是第一次添加,如果是直接把扩容值设置为10,然后用10这个变量走接下来的流程。在grow
方法中oldCapacity为0,经过计算后的newCapacity也为0,则第一个if判断为true,进入其中,最后把newCapacity赋值为10,然后复制数组,结束扩容流程。
ArrayList
中的数组复制方法
System.arraycopy()
方法
1 |
|
Arrays.copyOf()
方法
源码
1 |
|
所以copyOf()
本质上也是调用了System.arraycopy()
的方法。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!