JDK源码之 ArrayList
Posted 程序员专栏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK源码之 ArrayList相关的知识,希望对你有一定的参考价值。
00 前言
ArrayList 是我们日常开发中使用非常频繁的一个集合,今儿我们通过源码来看看它底层是怎么来实现的,了解了解它的优缺点和真正适合的场景。
01 内部的组成
privatestaticfinalint DEFAULT_CAPACITY = 10;
-
// 用于空实例的共享空数组实例。在初始化时,你传进去的初始化大小为0,那么就用这个来做的处理。
privatestaticfinalObject[] EMPTY_ELEMENTDATA = {};
-
//共享的空数组实例,用于默认大小的空实例,默认构造器使用的。
privatestaticfinalObject[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
-
transientObject[] elementData;
-
02 构造函数
如果我们直接 new ArrayList() 使用默认的构造函数来创建它,那么它会直接使用一个空的 Object [],初始大小是0,它有一个默认的初始化大小参数 DEFAULT_CAPACITY 是 10,当添加第一个元素时,这个空数据的大小才是10,我们也可以认为它给我们创建的默认的数据大小就是10 。
平常我们一般在使用 ArrayList 的时候,会使用另外一个构造函数,可以带初始容量进去的。一般来说是可以预估出来数据的大小,比如几十个,一百个,那么我们在初始化的时候就把这个数字带进去,避免数据容量太小在后续插入数据的时候进行频繁的扩容,创建新的数组。
扩容这块放在后面说,先看它里面比较核心的几个方法。
03 add() 方法
add() 方法很简单,先去校验需不需要扩容,然后把数据的 size 位置设置为元素,最后返回true;举个例子,比如我现在有这么一段代码需要往数据中插入3个元素:
List<String> list = newArrayList<>();
最开始 elementData = [],是一个空的数组,size = 0,当 list.add("张三") 时,elementData[0] = "张三",size++ 后,size 的值就为 1 。
然后 list.add("李四"),elementData[1] = "李四",当size++ 后,size 的值为 2,此时 elementData 的数据就是 ["张三","李四"] 。
依次类推,当 list.add("王二") ,elementData[2] = "王二",添加了王二这个数据后,elementData = ["张三","李四","王二"] ,size = 3。
04 set() 方法
如果你现在进行 set(3,"麻子") ,首先先进行 rangeCheck(index),检查给的索引是否越界,此时数组的个数是3,但是索引下标还是2,没有索引为 3的 这个位置,就会判定为越界。
如果是 set(1,"麻子") 是没问题的,E oldValue = elementData(index) 这段代码会先把索引为 1 这个位置的数据 “李四” 取出来,那么 oldValue = “李四”;
然后把 elementData[1] = “麻子”,此时 elementData = ["张三",“麻子”,“王二”],最后把 oldValue 的数据 “李四” 返回。
05 add(index,element) 方法
比如我们现在要在下标为 1 的位置添加“光头强”这条数据
首先 rangeCheckForAdd(1) 会对传进来的 index 为 1 进行是否越界的处理 。接下来执行 ensureCapacityInternal(size + 1) 操作,是做数组扩容的,后面说。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
此时的index = 1 ,size = 3 , 我们来转化一下:
System.arraycopy(elementData, 1, elementData, 2, 2);
那么这段代码表示的意思就是说 :elementData 这个数组,从第1位开始(第二个元素)拷贝,拷贝2个元素,把他们拷贝到 elementData 这个数组(还是原来的这个数组)从第2位开始(第三个元素)。
然后 elementData[index] = element ,把 elementData[1] 设置成 “光头强”,最后 elementData 的数据就是 ["张三","光头强","麻子","王二"]。
06 get() 方法
get() 方法应该是这里面最简单的了,首先先校验index 是否越界,然后直接调用 elementData(index) 方法,这个方法就是调用数组的取值。
由于 elementData[index] 是直接通过数组直接定位这个元素,然后直接获取这个元素的,这个也是ArrayList 性能最好的一个操作,优点。
07 remove(index) 方法
remove() 方法首先还是会校验索引是否越界,然后取出索引位置的数据,然后根据删除位置来计算需要移动元素的数量,如果移动需要移动元素的数量大于0 ,则使用 System.arraycopy 进行元素的移动,然后再把最后一个元素设置为 null,最终返回 删除的元素。
例如 我们要删除 index = 1 的元素 ,remove(1),当前数组的长度 size 是 4 , oldValue = elementData(1) = "光头强"。
numMoved = size - index - 1,numMoved = 4 - 1 -1 = 2,相当于要把后面的 “麻子”,“王二” 都要往前面移动一位。
接下来是 System.arraycopy(elementData, index + 1, elementData, index, numMoved) ,转换过来则为 System.arraycopy(elementData, 2, elementData, 1, 2)。
意思是说把 elementData 数组中,从 index = 2 的位置开始的元素,一共有 2 个,拷贝到 elementData (还是原来的数组),从 index = 1 开始,进行拷贝。
拷贝前的 elementData = ["张三","光头强","麻子","王二"]
拷贝后的 elementData = ["张三","麻子","王二","王二"]
然后再执行 elementData[--size] = null ,转化过来就是 elementData[3] = null ,把数组最后一个位置设置为 null。最后得到的 elementData = ["张三","麻子","王二"]。
08 数组扩容
ArrayList 里面最关键的一块儿,就是数据装满以后如何进行扩容的。
假设说我们现在用的数组是默认的大小,也就是10 ,现在数据里面已经装满了10个元素,此时数组的size =10,capacity = 10。
此时我们要调用 add() 方法 插入一个元素,插入第11个元素的时候是不行的,因为数据已经满了,此时就需要扩容。
我们在 add( ) 方法中可以看到这样一行代码 :ensureCapacityInternal(size + 1);
此时 ensureCapacityInternal(11),minCapacity = 11。
调用 calculateCapacity(elementData, minCapacity) 后得到 minCapacity = 11
进入到 ensureExplicitCapacity(),会发现 minCapacity - elementData.length 是大于 0 的,所以会进入到 grow() 方法里面,这里面就会进行扩容。
首先 oldCapacity = 10,newCapacity = oldCapacity + (oldCapacity >> 1),oldCapacity >> 1 相当于 oldCapacity/2 也就是 10 / 2 =5,那么 newCapacity = 15。
此时新的数组 就变成了可以容纳15个元素的数据,老的数组是 10个。最后使用 Arrays.copyOf() 工具方法来完成老数组到新数组的拷贝。
看到这里你就会发现数组扩容它是怎么来扩的,是老数据的大小+ 老数组大小 >> 1(相当于除以2) 来计算出来新数组的 大小,再使用 Arrays.copyOf() 来实现数据的拷贝。
09 总结
缺点一、若我们要频繁的向ArrayList 里面塞数据,会导致它频繁的数据扩容,会导致系统性能下降,所以说不要频繁的向ArrayList 插入数据。
缺点二、ArrayList 基于数组来实现的,数组你要想往中间插入一个元素,那么你需把插入位置后面大量的元素都向后面挪动一位,性能较差。
再说优点、基于数组实现,非常适合的是随机读取,你可以随机读取任意一个位置的元素,它是基于索引,下标来直接定位元素在内存中的位置的。
我们在开发系统的时候还是会大量用到ArrayList 的,很多时候我们需要一个集合按照顺序来放一些数据,它最主要的功能就是它里面的元素是有顺序的,会按照我们插入的顺序来排列。
本文是学习石杉老师JDK源码课的一篇输出总结,结合老师的教案文档看了一遍源码,然后自己做了些补充,希望对你有用。
近期精彩内容推荐:
以上是关于JDK源码之 ArrayList的主要内容,如果未能解决你的问题,请参考以下文章
JDK 源码解读之 ArrayList
死磕JDK源码之ArrayList
JDK源码之 ArrayList
JDK源码之ArrayList
JDK源码集合源码目录,冲冲冲
JDK源码那些事儿之常用的ArrayList