ArrayList源码分析

文章详细分析了Java中的ArrayList类,包括其常量属性如serialVersionUID、DEFAULT_CAPACITY、EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的作用。ArrayList使用Object数组elementData存储数据,size记录元素数量。文章讨论了不同构造器的行为,特别是空参构造器和带容量参数的构造器如何初始化elementData。在添加第一个元素时,如果使用空参构造器,会扩容至默认容量。文章强调了预估初始化容量的重要性,以减少扩容带来的性能影响。

ArrayList分析

 

java

复制代码

private static final long serialVersionUID = 8683452581122892189L; /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;

ArrayList里常用6个属性:

四个常量属性:

serialVersionUID:是用于在序列化和反序列化过程中进行核验的一个版本号。

DEFAULT_CAPACITY:默认的初始化容量10

EMPTY_ELEMENTDATA:空实例时的数组实例

DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认大小的空实例时的数组实例

前两个解释的比较抽象

elementData:Object数组,集合真正用来存数据的容器。

size:集合大小

关于serialVersionUID可以看一下这篇博客。

serialVersionUID 是干什么的? - 知乎 (zhihu.com)

ArrayList的三个构造器(构造方法):

 

java

复制代码

public ArrayList() //空参 public ArrayList(int initialCapacity) //带有初始化容量 public ArrayList(Collection<? extends E> c)//以一个集合来初始化

因为ArrayList集合底层就是采用elementData[]数组来存储数据,在创建集合时:

1、使用空参构造器时。

只是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋值给了elementData数组来完成集合的初始化,因为DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前创建好的,所以在创建集合时的操作只是一个赋值操作,简化了创建数组的时间。

 

java

复制代码

transient Object[] elementData; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

2、使用有参(initialCapacity)构造方法时。不废话,先看源码:

他会首先判断传进来的initialCapacity是否是大于0的:

如果不大于0他执行的操作和使用空参构造器执行的操作类似,只是这次赋值给elementData数组的是EMPTY_ELEMENTDATA数组,此次空数组赋值竟然和上次空参不一样,这是为什么呢?为什么要创建出两个空数组呢?暂且按下不表

如果大于0,他会新创建一个initialCapacity大小的数组赋值给elementData。至于传进来的是一个小于0的数,当然结果就是死路一条。

3、构造方法参数是集合时。

 

java

复制代码

public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }

他会首先将传进来的集合转化为一个Object数组,然后判断该数组是否为空。

如果为空,他仍会执行给elementData数组赋值EMPTY_ELEMENTDATA数组的操作,到这有没有发现奇妙的一点?无论集合第一次创建时采用的哪一个构造方法,如果传进来的是一个“空内容”,他都会使用底层已经创建好的数组来完成初始化

如果不为空,判断他们两个是否是同一个集合。如果是,将a数组直接赋值给elementData。如果不是,将a数组复制到elementData

问题:

为什么ArrayList源码里要定义两个空数组呢?直接定义一个岂不是更节省空间?

可以看一下源码中对DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的注释:

We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

大致意思就是:是为了在第一次添加元素时判断去给数组inflate(扩容)多少。 这就涉及到了集合在第一次添加元素时的操作和集合的扩容。

以下时第一次添加时的源码。

 

java

复制代码

public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }

 

java

复制代码

private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }

 

java

复制代码

private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }

 

java

复制代码

private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }

在第一次添加元素时会先调用ensureCapacityInternal方法,同时将添加一个元素后的集合大小size传参过去,在ensureCapacityInternal方法中调用ensureExplicitCapacity()方法,在执行此方法之前,会先调用calculateCapacity方法。如果此时elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,也就是采用的空参构造器的初始化的第一次添加,则返回默认容量和当前容量的较大者,当然第一次添加肯定是返回默认容量(DEFAULT_CAPACITY)。最后进入ensureExplicitCapacity方法,根据minCapacity - elementData.length > 0 判断容量是否足够,然而判断是否要执行扩容方法grow,;

走到这就会发现,ArrayList的初始化容量不会在创建集合时进行,而会在第一次添加元素时进行。而且只有采用的是空参构造方法时,才会在第一次添加元素时将最小容量设置为minCapacity = DEFAULT_CAPACITY = 10。

这还不是最终的扩容,最终的扩容在grow方法中:

 

java

复制代码

private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//让newCapacity等于原来容量的1.5倍 if (newCapacity - minCapacity < 0) //如果newCapacity比minCapacity小 newCapacity = minCapacity; //让newCapacity等于minCapacity if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }

建议

因为ArrayList每扩容一次都要将原数组数据复制到新数组,所以建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值