ArrayList的注意事项:
(1)允许所有的元素,包括空元素,ArrayList可以加入空元素,并且多个
我们的实例代码如下所示:
package com.rgf.collection; import java.util.ArrayList; import java.util.List; public class ArrayListDetail { @SuppressWarnings({"all"}) public static void main(String[] args) { ArrayList arrayList = new ArrayList(); arrayList.add(null); arrayList.add("jdbc"); System.out.println(arrayList); arrayList.add(null); System.out.println(arrayList); } }
运行界面如下所示:
(2)ArrayList是由数组来实现数据存储的
(3)ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrayList.。面对多线程我们可以使用Vector。
我们查看源码如下所示:
//ArrayList是线程不安全的 //我们进入ArrayList源码的界面,找到诸如它的方法的源码如下所示: //我们看到该源码并没有synchronized。 //synchronized:我们在遇到线程安全问题时,了解到导致该问题的原因就是由多个线程同时处理共享资源所导致的。所以我们必须保证在任何时刻只能有一个线程访问共享资源。我们可以将处理共享资源的代码放在一个使用synchronized关键字修饰的代码块中,这个代码块被称为同步代码块。 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
ArrayList的底层操作机制源码分析:
结论:
(1)ArrayList中维护了一个Object类型的数组elemenData
我们利用add方法往list集合加入数据的时候,这些数据我们最终存储在ArrayList的一个属性elemenData数组中,这个数组也是一个Object数组。
transient Object[ ]elementDate; //transient 表示瞬间,短暂的。表示该属性不会被序列化(序列化是将对象的状态信息转换为可以存储或传输的形式的过程)
我们进入源码查看:
我们发现elementDate类型为Object[ ]数组,而Object[ ]数组意味着我们什么都可以往里面存放,Object是所有类的父类,是一个顶级类。
(2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementDate容量为0,第一次添加,则扩容elementDate为10,如需要再次扩容,则扩容elementDate为1.5倍。比如我们往里面放东西,刚开始扩容为10,放满之后,我们继续扩容,为10的1.5倍,即15。如果又放满之后,我们扩容15的1.5倍。诸如此类。(这是jdk1.8,而jdk1.7里面初始化为一个容量为10的数组。
ArrayList里面有三种构造器,ArrayList(int),在这个地方可以传进去一个整数。
(3)如果使用的是指定大小的构造器,则初始elementDate容量为指定大小,如果需要扩容,则直接扩容elementDate为1.5倍。
我们使用构造器ArrayList(int),如果我们刚开始指定为8,初始化传入的int为8,elementDate的大小为8,如果我们填满之后,进行扩容的时候,是在8的1.5倍即为12进行扩容。如果12个也满了,我们在12的1.5倍上即为18进行扩容。
ArrayList无参构造扩容底层源码分析:
我们进入源码进行分析:(我们如果进不去源码可以进行如下设置)取消勾选Do not stop into the classes。
我们利用如下代码进行Debug来进行对源码进行分析:
package com.rgf.collection; import java.util.ArrayList; @SuppressWarnings({"all"}) public class ArrayListSource { public static void main(String[] args) { //使用无参构造器创建ArrayList对象 ArrayList list = new ArrayList(); //使用for循环给list集合添加1-10数据。 for (int i = 1; i <= 10; i++) { list.add(i); } //使用for循环给list集合添加11-15数据。 for (int i = 11; i <= 15; i++) { list.add(i); } list.add(100); list.add(200); list.add(null); } }
(1)我们进行如下操作:
分析: //使用无参构造器创建ArrayList对象
ArrayList list = new ArrayList();我们点击Debug之后,点击蓝色向下小箭头之后,如下所示:
我们点击向下的弯的箭头,如下所示:
我们在进入elemenData的源码界面进行查看:
分析使用无参构造器,创建和使用ArrayList的源码。
创建了一个空的elementDate 数组={ }
通过这个语句,我们可以看出来elementData进行第一次初始化的时候就是一个空数组。
(2)分析执行list.add方法源码:
//使用for循环给list集合添加1-10数据。
for (int i = 1; i <= 10; i++) {
list.ad
}我们继续进入源码进行查看:我们先进行装箱
我们退出再一次进入的时候:
再添加之前首先调用了ensureCapacityInternal这个方法,确认容量是否够,即先确定是否要扩容。再执行赋值的操作(先赋值后++)
ensureCapacityInternal方法确定最小容量。第一次扩容为10。
我们进入ensureCapacityInternal这个方法之后,我们发现调用的是
calculateCapacity(elementData, minCapacity)方法,我们继续对该方法进行判断:private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
我们查看该方法,先确认elementData是不是一个空数组,如果elementData第一次添加是一个空数组,我们赋一个最小容量,而这个最小容量是DEFAULT_CAPACITY和 minCapacity这两个值的最大值。
这就是我们使用无参构造器扩容的时候
我们利用calculateCapacity(elementData, minCapacity)方法先确定一个真正的minCapacity方法,之后我们利用ensureExplicitCapacity(calculateCapacity(elementData, minCapacity))确定是否要真的扩容。
我们再继续往下走:
我们发现此时的minCapacity已经确定了,为10.而里面的modCount为记录当前集合所被修改的次数。防止多个线程同时去修改他。
如果我的需要的最小容量-当前数组实际的大小>0,说明数组容量不够,满足这个条件之后,即进行底层的真正的扩容,即调用grow()去扩容。
真的扩容
使用扩容机制来确定要扩容到多大
第一次newCapacity=10
第二次及其以后,按照1.5倍扩容。、
扩容使用的是Arrays.copyOf。copyOf会保留原先的数据,扩容之后,再原先数据的基础上去增加null。
我们进行一步步的进行调用代码:
private void grow(int minCapacity) { // overflow-conscious code //我们先把elementData.length记录到oldCapacity这个变量里面去。这个时候仍然为0. int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //原先数组大小+原先数组大小/2.如果我们把这个值向右移动1位,就相当于是除以2,第一次的时候还是为0,并没有真正使用到1.5倍的扩容机制。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //把10赋给newCapacity。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //如果所创数组比最大值(2147483639)还要大,我们利用hugeCapacity(minCapacity)方法进行处理。 // minCapacity is usually close to size, so this is a win: //Arrays.copyof为数组的赋值,也就是说elementData为一个空数组,扩大到newCapacity=10,扩完之后,里面有10个空数据。 elementData = Arrays.copyOf(elementData, newCapacity); }
扩容完成之后,会逐步返回到我们的add方法,继续运行如下所示:
如果我们再继续运行过程中,发现如下所示:此时不满足扩容条件时,就不用扩容。
我们进入下一个for循环如下所示:
我们发现此时容量不够·,需要进行扩容,我们此时容量为10
扩容之后的大小为15,即为10的1.5倍。
扩容之后的数据为1-10,后面有5个null。
扩容成功:
IDEA在默认情况下,Debug显示的数据是简化后的,如果希望看到完整的数据,需要做设置。
设置完成之后,我们可以更加细致的看到断点情况:
开发中使用带参的构造器:ArrayList list=new ArrayList(int capacity)
ArrayList有参构造扩容:
如果使用的是指定大小的构造器,则初始elementDate容量为指定大小,如果需要扩容,则直接扩容elementDate为1.5倍。
我们使用构造器ArrayList(int),如果我们刚开始指定为8,初始化传入的int为8,elementDate的大小为8,如果我们填满之后,进行扩容的时候,是在8的1.5倍即为12进行扩容。如果12个也满了,我们在12的1.5倍上即为18进行扩容。
我们设置的代码如下所示:
package com.rgf.collection; import java.util.ArrayList; @SuppressWarnings({"all"}) public class ArrayListSource { public static void main(String[] args) { //使用无参构造器创建ArrayList对象 ArrayList list = new ArrayList(8); //使用for循环给list集合添加1-10数据。 for (int i = 1; i <= 10; i++) { list.add(i); } //使用for循环给list集合添加11-15数据。 for (int i = 11; i <= 15; i++) { list.add(i); } list.add(100); list.add(200); list.add(null); } }
我们进行Debug之后如下所示:创建了一个指定大小的elementData 数组={ }
this.elementData = new Object[initialCapacity];
我们运行到9的时候,我们发现进行了扩容,之前的不需要扩容,如下所示:
如果是有参的构造器,扩容机制:
(1)第一次扩容,就按照elementData的1.5倍进行扩容
(2)整个执行的流程还是和前面一样的。