欢迎来到硬啃世界
你好,希望你每天给自己一点信心和耐心,不做被公司、面试者、社会淘汰的程序员,在这里硬啃汉带你去硬啃源码重新捡回信心。
硬啃前的一些准备
- JDK版本我们用1.8的,现在还是主流的版本。
- 因为如果我们不做任何配置的话,我们debug的时候跳转到JDK源码是看不到我们传进去的值,这个原因可以百度一下,大致就是因为减少JDK的大小,配置步骤看如下帖子:
解决Eclipse调试JDK源码无法查看变量值
开始硬啃
我们先写一个循环,循环很简单,就是不停的往List里面添加元素,至于为什么是循环11次,等会说
public static void main(String[] args) {
List<String> list = new ArrayList<>();
String name = "哇哈哈";
for (int i = 0; i < 11; i++) {
if (i==10) {
list.add(name+i);
}else {
list.add(name+i);
}
}
}
然后把断点打到new ArrayList这行,然后运行main方法,然后我们进入这一句new的时候看看发生了什么
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 这里有个很少见的关键字:transient。
- 主要作用就是当我们序列化这个对象的时候,该关键字不被序列化。
这里我们看到他是初始化了一个内部数组 elementData ,其实我们平时用的add、remove等方法都是在操作这个数组,所以ArrayList的底层数据结构就是个数组,这是重点面试会问。
我们断点接着走,进入add方法,我们看看添加元素的时候做了什么操作
private int size;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这里先调用了ensureCapacityInternal方法,然后我们看到一个新的变量 size ,他是用来记录该ArrayList存在多少个元素的。
我们来看看ensureCapacityInternal内部逻辑:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
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);
}
我们按照顺序,从上到下,一个一个方法分析:
- 判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这里不就是刚开始初始化的时候那个空数组嘛,其实这里就是判断是否第一次操作这个ArrayList,如果不是就不会进入判断内的逻辑,然后是的话,就将DEFAULT_CAPACITY变量和当前minCapacity做比较,最大的值重新赋值给minCapacity,我们看到DEFAULT_CAPACITY是等于10的,后面再详说。
- 然后调用ensureExplicitCapacity() 方法,minCapacity作为入参传入。
- modCount++,这个的意思就是操作次数+1,modCount就是用来记录操作这个ArrayList的次数的。
- 然后判断minCapacity - elementData.length 是否大于0,换句话就是我准备添加的元素总数是否比你原来的数组大,如果是,放不下了,调用 grow() 方法开始扩容。
- 我们来看看如何扩容的,int oldCapacity = elementData.length,获取当前数组长度。
- 计算得到扩容后的大小newCapacity ,oldCapacity加上oldCapacity右移1位,这里采用了位的操作,比方说oldCapacity原本是10,右移之后就是5,那么这里的意思就是每次扩容都是原来的长度的一半,原本是10个元素,现在需要扩容,就是扩容15个长度,现在30个元素,需要扩容最终就是45个长度,以此类推。
- 然后判断newCapacity - minCapacity < 0,第一次添加元素的时候newCapacity是等于0的,0右移1还是0所以这里减minCapacity肯定会小于0,然后我们知道前面minCapacity 已经是等于10了,然后这里将minCapacity 赋值给newCapacity,这里就说明我们的ArrayList初始长度是等于10的,但是不代表现在大小就是10,前面也说过,size用来表示ArrayList当前有多少个元素,这里的长度只是说我们的这个ArrayList可以放多少个元素。
- 接着判断newCapacity - MAX_ARRAY_SIZE > 0,这里就是判断扩容后的长度是否超过最大允许的长度2147483639,我相信正常没人会用个ArrayList存这么多的元素,真有这样的人建议原地转行。
- 最后就是elementData = Arrays.copyOf(elementData, newCapacity) 就是帮我们创建一个newCapacity长度的新数组并把原来的elementData元素复制到新数组并重新赋值覆盖elementData 。
- 到此,判断是否扩容,然后到怎么扩容就结束了。
执行完ensureCapacityInternal方法判断是否需要扩容操作之后,回到add方法,接下来就很简单了,elementData[size++] = e,将我们需要添加的元素添加到对应size下标的位置,并且然后size自增1,代表现在已经有一个元素了,然后return true;
到此我们得ArrayList调用add方法添加元素得源码就分析完了,我们总结一下:
- ArrayList数据结构是一个数组。
- ArrayList初始长度是10。
- ArrayList每次扩容都是原来的大小的一半,并且采用的是右移1的方式(长度 >> 1),因为计算性能比直接长度 / 2要好。
- ArrayList不是线程安全的,因为我们通过源码分析并没有发现任何像同步块或者是加锁的操作,如何想使用线程安全的ArrayList可以用CopyOnWriteArrayList,底层也是一个数组,但是比较骚的是,这个每次添加元素直接就扩容一个长度,然后add方法是采用ReentrantLock类方式进行加锁,后面另外开个帖子说下这个。
记住这三点面试别人问你ArrayList的都不虚,因为这是最简单的一个集合了。