1. ArrayList默认初始化容量
首先编写一个简单的初始化ArrayList的代码
List<String> li = new ArrayList<>();
然后进入ArrayList中,在无参数构造方法中可以查看到上面的绿色注释中写了构造一个空的集合并且初始化容量为10。接下来继续查看源码,分析这个10是如何来的。
这里面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA可以查看到在调用无参数构造方法时,默认的初始化容量为0。
当第一次向这个集合中添加元素
li.add("a");
这个时候debug去跟踪一下这个添加元素的过程。首先进入的是add()方法
然后接着进入下一步,就进入到了这个add()方法中,此时注意到这个s == elementData.length,条件为true,因此进入到下面的grow()方法中
第一次进入的这个grow()方法不是最终的,还需要看return后面的grow()方法
进入到这里的grow()方法之后,可以看到oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个条件式值为false,因此走到下面return语句。可以看到在这里会新创建一个数组。这个时候容量为DEFAULT_CAPACITY和minCapacity中的最大值,可以看到此时DEFAULT_CAPACITY的值为10,而minCapacity的值为1,因此最大值即为10。
所以得到的结论是,在第一次创建ArrayList数组时,这个时候的容量为0,在第一次调用add()方法添加元素时,才会初始化容量为10。
2. ArrayList扩容策略
List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");
//添加第11个元素
li.add("a11");
通过上面的分析,得知了ArrayList的初始化容量为10,那么当添加进入第11个元素时,此时的容量超出了10,这个时候ArrayList就需要进行扩容的操作, 继续对源码进行分析,查看ArrayList是如何进行扩容的。
上面的步骤还是一样,但是到这里的时候,oldCapacity的值为10,因此oldCapacity > 0为true,因此进入到条件里面。这个时候查看到newLength()方法里有三个参数,oldCapacity,minCapacity - oldCapacity和oldCapacity >> 1。oldCapacity为原容量,值为10;minCapacity - oldCapacity为最小增长,值为1;oldCapacity >> 1为预期增长,值为5。
接下来进入newLength()方法,这个时候预期长度prefLength = oldLength + Math.max(minGrowth, prefGrowth),oldLength即为原先容量10,然后再加上最小增长minGrowth和预期增长prefGrowth中的最大值,可以看到,此时的最大值为5。因此预期的长度即为10 + 5 = 15。
确定好预期长度为15后,又回到上一步,这个时候会进行数组的扩容。扩容的方式为将原先的元素和新的容量15传入到copyOf()方法中。
所以总的来说,ArrayList的扩容策略为先创建一个新的数组,新数组的容量为原先容量的1.5倍,然后再将原先的元素进行拷贝。
3. ArrayList的添加、修改、插入和删除元素
3.1 添加元素
添加元素在上面分析初始化容量和扩容策略时已经大概的分析过了,这里就不细说了。
3.2 修改元素
List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");
li.set(1, "a22");
在修改元素时,由于传入了下标,因此会先对下标是否越界做出判断,如果没有越界则先将原先的值赋值给oldValue,再将新值赋值给指定下标的元素,最后返回原先的元素oldValue。这样修改元素的操作也就完成了。
3.3 插入元素
List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");
li.add(1, "a22");
在进行插入元素时,和上面一样,依旧是先进行下标是否越界的判断,如果没有越界,则进行下一步。在执行插入操作的时候,注意红框中的方法,是进行了一个拷贝的操作。
就是从要插入元素的位置开始,比如现在需要插入的位置为1,则从1开始一直到后面所有的元素都进行拷贝,然后从插入元素的位置的下一个位置,也就是第2个位置,将拷贝的那些元素粘贴进去。最后再将需要插入的值赋值给空出来的这个位置,也就是1这个位置。
3.4 删除元素
List<String> li = new ArrayList<>();
li.add("a1");
li.add("a2");
li.add("a3");
li.add("a4");
li.add("a5");
li.add("a6");
li.add("a7");
li.add("a8");
li.add("a9");
li.add("a10");
li.remove(1);
在执行删除元素操作的时候,检测完下标没有越界之后,会先将数组赋值给临时变量es,然后进入fastRemove()方法。
进入到这个方法后发现,其实在删除元素的时候,也是进行了一个拷贝的操作。比如我现在要删除i = 1这个位置的元素,那么就从i + 1也就是2这个位置开始的元素进行拷贝,然后复制到新的数组中,在新的数组中从1开始往后面赋值。最后再将这个新数组的最后一个元素置为空。
总的来说,不管是插入元素还是删除元素,都需要将这个元素之后的所有元素进行一个整体的挪动,因此ArrayList在进行插入和删除元素的时候,效率不是很高。