背景
之前关注了一个公众号“彤哥读源码”,跟着一起学习了jdk里的很多类的源码,包括集合类、线程类、并发类。真的是很好的公众号,哈哈(因为确实是很好,这里愿意给路过的小伙伴安利一下,纯自己主观推荐,如果对公众号作者有帮助,算是感谢这么好的文章对我的帮助了~)
其中,ArrayList是最简单的一个了,但是读完别人写的东西其实还是不如自己实践来的实在。所以自己再搞点东西。
文章链接(侵删):https://mp.weixin.qq.com/s/a0zq-q8JuSwsLYX7tJ4VxA
计划
准备按照一个路径走,边看文章,边看源码,然后自己手动做些调试。顺便写点文章,记录一下学习和调试过程。
之后,自己手动实现一个ArrayList,可以根据情况进行多版本迭代。
正文
成员变量很简单,文章中也有介绍。ArrayList的元素存储在Object[] elementData数组中,size是当前保存了多少个元素。
还有两个静态final的空数组EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA,看到这个就会产生疑惑,为什么会需要两个静态空数组呢?都是final的,反正不可变,用一个不就可以了?这其实跟构造函数有关系。
构造函数
ArrayList有三个构造方法,ArrayList()、ArrayList(int initialCapacity)、ArrayList(Collection c)。
ArrayList()会默认使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA作为elementData的初始值;
ArrayList(int initialCapacity),如果initialCapacity > 0 就直接用这个值初始化数组长度了,等于0就直接用EMPTY_ELEMENTDATA。
ArrayList(Collection c) 如果c里没有元素,默认也是用EMPTY_ELEMENTDATA。
(上面说的很啰嗦,看代码一目了然)
增长逻辑
此时重点来了,如果用ArrayList()和ArrayList(0)实例化了两个对象,它们在增加第一个变量的时候,elementData会怎么进行扩容?
这里的判断告诉我们它是怎么做的。如果不手动指定初始容量,在第一次就会把容量设置为10。如果手动指定的初始容量是0,那么第一次就会把容量设置为你需要的那个值(调用add方法的时候,其实就是1)。
一开始我产生了额外的疑问:为什么代码写的这么啰嗦?minCapacity不就是1吗,还能大于10?后来看了一下,确实能大于10,看一下addAll(Collection c)这个方法就明白了。如果一下子加入很多元素,也是会调用这个方法的。
那么这个逻辑我们怎么验证一下呢?elementData是default的,自己写代码也没权限获取。这时候就是祭出“反射”大法的时候了,毕竟java里“万物皆可反射”😏
import java.lang.reflect.Field;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 通过使用ArrayList()和ArrayList(0)来进行对比
ArrayList<Integer> list = new ArrayList<>(0);
Field field = list.getClass().getDeclaredField("elementData");
field.setAccessible(true);
Object []ele = (Object[]) field.get(list);
System.out.println(ele.length);
Field field1 = list.getClass().getDeclaredField("EMPTY_ELEMENTDATA");
field1.setAccessible(true);
Object []ele1 = (Object[]) field1.get(null);
// 这里能发现ele和ele1是同一个对象
System.out.println(ele);
System.out.println(ele1);
System.out.println(ele1 == ele);
// 逐渐增加元素,会发现容量每次增长一半
list.add(1);
list.add(1);
list.add(1);
list.add(1);
list.add(1);
System.out.println(list.size());
// 这里不能打印ele.length,想想为啥~(一开始就打错了。。。)
// 即便一开始的容量是大于0的,比如20,当元素增长超过20的时候,也不能用ele了,因为复制产生了新数组
System.out.println(((Object[]) field.get(list)).length);
}
}
总结:
(1)如果不指定容量,新加入元素的时候容量最小是10;如果指定了容量为0,新加入元素的时候,容量是加入元素的个数;
(2)之后的容量增长都是每次增长一半。10->15->22->33 或者 0->1->2->3->4->6->9
(3)万物皆可反射~