下面我想通过一个demo来跑一下ArrayList的add操作,一步一步来分析一下每个方法
首先来看一下这个构造方法,指定容量大于0,就初始化一个新的数组,大小为指定的容量,这里是1;容量为0,elementData指向空数组;小于0直接抛异常。
下面就是执行add方法。
可以看到,真正进行存储数据的是在方法的第二行,第一行是一个确保容量的方法,主要是在进行存储数据的时候,进行一个容量的判断,即是否扩容。我们进入这个方法:
这里又封装了一层,继续点进去其中的calculateCapacity方法,计算最小容量
这里我们传入的参数叫做minCapacity,叫做最小容量,意思就是我们要插入第0个元素,那么最小容量就是1,因为要保证至少有一个位置保存我们的数据,这里的minCapacity我们看上面add方法,就是传的size+1,要插入一个元素,就要保证数组第size+1的位置是没有被占用的。
来看下这个方法,if语句判断我们的list初始化声明的时候有没有指定容量,如果没有,即用的是默认的容量,就返回默认容量和minCapacity中较大的那个,否则就直接返回minCapacity。
接着往下执行,我们来看一下ensureExplicitCapacity这个方法。
首先操作数自增,表示进行了一次修改数据的操作,下面是比较重要的一个方法,也就是我们的扩容机制,判断一下我们所需的最小容量是否已经超过了数组的大小,超过了就要进行扩容,这里我们一开始初始化的容量是1,这里增加第一个字符串lunzi,是不需要扩容的,增加第二个字符串my的时候就需要进行扩容,我们进入扩容的方法
扩容的整个思路就是通过一个新的扩容的数组,将旧数组的数据复制进去。首先定义了一个变量,存储旧数组的长度,下面一句是定义新数组的长度,这里1.7 1.8的jdk都是通过位运算,相当于扩容到原来长度的1.5倍,1.6的做法是直接通过int newCapacity = (oldCapacity *3)/2+1,扩容为原来的1.5倍加一,这里位运算的速度是要比整除效率高的。下面的第一个if是考虑了一个溢出,int是有范围的,超出了整个范围之后,扩容的时候会溢出,变成一个负数,这时候就不需要扩容那么大的了,直接是最小容量就行了,下面一个if是判断扩容后的数组是否超过了最大值,超过了之后我们就要调用hugeCapacity()来比较minCapacity和MAX_ARRAY_SIZE。
如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为minCapacity。但是几乎不会出现这种情况,因为上面也说了,一般用list很少很少存储这么大的数据量,要是这么大早就写进数据库了,或者其他持久化的方式了,这辈子我估计都调用不到一次。
关于int类型溢出这边,希望大家去看一下二机制的运算原理,会有一个更加清楚的认识。
到这边整个add操作就完成了。
我们知道add还有一个可以指定位置插入的方法:
这里上面比指定位置的add方法只是多了一些实现,一开始先进行判断index是否合法,然后确保容量之后有一个copy的过程,调用了System.arraycopy这个方法,读起来也非常简单。