用于测试的代码如下
public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 10; i++) { list.add(i); } list.add(20); list.add(30); list.add(40); }
首先创建一个ArrayList集合,这里我用的是无参构造,有参构造在下面进行讨论
通过deBug我们进入到集合内部发现是调用的集合的无参构造
//ArrayList的无参构造源码, public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是ArrayList集合中的属性,这里elementData前面有一个修饰符transient,这个作用就是放置属性被序列化的.
elementData就是我们ArrayList底层用于存放数据的数组,在JDK8时ArrayList在创建的时候,默认创建的是一个空数组,只有在第一次添加数据时才会对数组进行扩容
transient Object[] elementData; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
接下来执行add()方法
list.add(i);
//首先由于我们存入的是基本数据类型,而集合需要存放的是Object,所以java进行了自动装箱。 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
接继续下来进行调试
//这个是ArrayList的添加元素的方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
我们一点一点的对这个方法进行解释
ensureCapacityInternal(size + 1);//1 //首先我们对size+1,是因为在添加之前要先对数组进行判断是否还有空间 //接下我们通过断点进入方法 private void ensureCapacityInternal(int minCapacity)//minCapacity = 1 { //这个方法的作用就是确定数组的容量 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
第一步分析calculateCapacity
//进入calculateCapacity(elementData, minCapacity)方法 //这个方法的作用就是判断,数组是否为空,并且确定数组容量 private static int calculateCapacity(Object[] elementData, int minCapacity) { //首先判断数组是否为空数组,由于我们是第一次添加,所以数组为空数组,所以会进入if判断内部 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //private static final int DEFAULT_CAPACITY = 10; //这是我从ArrayList顶部复制得,很容易发现这是java定义的一个ArrayList的默认容量为10 //这里返回默认容量和最小需要的容量的最大值,显然最大值是默认容量10,这也是ArrayList默认容量为10的原因 return Math.max(DEFAULT_CAPACITY, minCapacity); } //如果不为空的话,也就是不是第一次进行添加,则返回的是最小需要的容量 return minCapacity; }
第二步分析ensureExplicitCapacity()方法
private void ensureExplicitCapacity(int minCapacity) { //这个数据用来判断集合被修改过了几次 modCount++; // overflow-conscious code //判断最小容量是否大于数组长度如果大于的话则进行扩容,否则不扩容 //我们的minCapacity最小容量为10,大于数组的长度0 if (minCapacity - elementData.length > 0) //这个才是最终的扩容方法 grow(minCapacity); }
第三步定位到grow(minCapacity)扩容方法
//ArrayList的扩容方法 private void grow(int minCapacity) { // overflow-conscious code //将数组的长度赋值给老的容量(oldCapacity) int oldCapacity = elementData.length; //这个就是ArrayList 1.5倍扩容的原因 //oldCapacity >> 1 就相当于除以2,所以这个新容量就是老容量+老容量/2,也就是1.5倍扩容 //由于我们第一次存放元素数组长度为0,所以老容量扩容1.5倍还是0 int newCapacity = oldCapacity + (oldCapacity >> 1); //接下来的判断就是为了防止我们第一次容量为0时,扩容后还是0 //if会让扩容后的容量和最小容量进行比较,当扩容后的容量还是小于最小容量,会将最小容量赋值给新容量, //我认为这一步的作用就是为了保证扩容后一定可以将这个元素添加到集合中。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //这一个就是判断集合的容量是否超过Integer的最大值 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //调用Arrays.copyOf()对数组进行扩容 elementData = Arrays.copyOf(elementData, newCapacity); }
第四步将元素存放到集合中
//将元素放在第0号位上并将size+1 elementData[size++] = e;
第五步插入完成返回结果
return true;
小结:
综上,ArrayList从创建到第一次加入数据的过程就完成了.
- 创建ArrayList对象,调用无参构造,将默认的空数组赋值给存放数据elementData
- 添加元素前会先进行为判断是否为空,如果为空的话并从最小容量(size+1)和默认容量(10)中取最大值,如果不为空的话,则直接返回最小容量
- 接下来判断是否需要扩容,让最小容量和数组长度进行比较
- 如果需要扩容的话,则会先将数组的长度赋值给老容量,然后将老容量扩大1.5倍并赋值给新容量,然后将下面很关键的一步,将新容量和最小容量进行比较,如果小于的话会将最小容量赋值给新容量.然后通过Arrays.copyOf()方法完成扩容
- 如果不需要扩容的话,则直接进行添加并将size++,最后return true。
接下来for循环内的添加基本上都是差不多,循环下面的添加让我们继续探究一下
list.add(20);
进入方法内部,接下来通过图片让大家更直觉的看到数据的变化
集合中的元素如下
进行集合非空判断
判断是否需要扩容
进行扩容
扩容后的集合
添加元素
经过上面的过程,我们很容易知道,ArrayList每一次都会先进行判断,从而保证每个数据都能成功添加进去
最后我们再看一下ArrayList的有参构造
public ArrayList(int initialCapacity) { //如果传入的值大于0的话则会创建一个大小为initialCapacity的数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0)//如果传入的值为0,则会将空数组赋值过去 { //private static final Object[] EMPTY_ELEMENTDATA = {}; this.elementData = EMPTY_ELEMENTDATA; } else//传入的值小于0的话则会抛出异常 { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
总结
博客写的不算完美,如果遇到文中错误,希望大家在评论区指出,大家相互进步,创作不已,点个赞吧!