继承:AbstractList
实现:List、RandomAccess、Clonable、Serializable
(文末附集合类的UML类图)
属性:
//数组初始容量大小
private static final int default_capacity=10;
//初始化空数组
private static final Object[] empty_elementdata={};
//默认共享数组空间
private static final Object[] defaultcapacity_empty_elementdata={};
//存储元素的数组缓冲区
transient Object[] elementData;
//包含的元素数量
private int size;
//数组最大长度,为什么要减8呢?数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节
private static final int max_array_size=Integer.MAX_VALUE-8;
构造方法:
//默认构造方法,会初始化一个空对象数组
public ArrayList(){
this.elementData = defaultcapacity_empty_elementdata;
}
//指定对象数组的初始容量,不能为负数
public ArrayList(int initialCapacity){
if(initialCapacity>0){
this.elementData = new Object[initialCapacity];
}else if(initialCapacity==0){
this.elementData = empty_elementdata;
}else{
throw new IllegalArgumentException("非法容量");
}
}
jdk1.8中,ArrayList初始化时,如果没有指定初始容量,那么默认会创建一个空对象数组(Object[]),此时size=0
1、直接调用add(E e)添加元素到末尾:
1.1、首先调用ensureCapacityInternal(int)方法检测是否需要扩容,该方法接收一个参数(size+1),也就是当前数组长度+1
1.2、如果是第一次添加元素(集合还是空的),那么会默认创建长度为10的对象数组(minCapacity=Math.max(default_capacity,size+1))
1.3、modCount++,修改标识会+1,表示结构被修改的次数
1.4、如果当前所需容量minCapacity超过对象数组的长度,就会调用grow(int)函数进行扩容
1.5、扩容时,newCapacity = oldCapacity + (oldCapacity >> 1);也就是说会扩容到原来长度的1.5倍,然后使用Arrays.copyOf(elementData, newCapacity)进行扩容。【重点】
1.6、完成扩容验证后,调用elementData[size++] = e,添加新元素到对象数组成功
2、添加元素到指定位置add(int index,E e):
2.1、首先通过rangeCheckForAdd(index)方法进行下标校验,如果超过当前size或者为负数,则报IndexOutOfBoundsException错误
2.2、调用ensureCapacityInternal()检测扩容(见1.1~1.5),刚刚2.1已经判断index
2.3、调用System.arraycopy()进行数组拷贝,将index及后面的元素全部后移1位
2.4、elementData[index] = e 将元素e添加到index位置,并且size++,长度自加
为什么1.5使用Arrays.copyOf()拷贝数组,而2.3中使用System.arraycopy()进行拷贝呢?
首先两者的区别是,System.arraycopy调用本地方法(C、C++)对原有数组拷贝,而Arrays.copyOf底层调用了System.arraycopy(),并且数组后面补0,最后返回了一个新数组
【在这里,两个地方使用不同的拷贝方法,是有原因的,但目前我未深入研究】
add扩容的证明:
List list = new ArrayList();
for(int i=0;i<20;i++){
if(i==10){
list.add("元素"+i);
}else{
list.add("元素"+i);
}
}
初始化时,arrayList的对象数组elementData为空,size为0:
添加第一个元素时,初始化elementData对象数组的长度为10:
添加第11个元素时,minCapacity(11)>DEFAULT_CAPACITY(10),那么就会进行自动扩容,扩容为原来的1.5倍:
1、按下标删除元素remove(int):
1.1、调用rangeCheck(index)判断下标是越界,注意,这里只判断index>=size就抛出异常,没有检测index<0的情况,而上面添加元素时,调用rangeCheckForAdd(index)判断了0<=index<=size,为什么两者不同呢?请看下面的1.3【重点】
1.2、modCount++增加结构修改次数
1.3、E oldValue = elementData(index)在这里将从对象数组里取出下标为index的值,在这里面已经做了index<0的判断,如果index为负数则会报ArrayIndexOutOfBoundsException错误。也就是说如果上面rangeCheck也判断了index<0的情况,那判断了两次index<0,这就造成:方法职责不单一、重复逻辑,而且降低了效率
1.4、还是利用System.arraycopy()将index后面的元素前移一位,直接将index所在元素覆盖,完成元素删除操作
1.5、这时候有同学就会想到,System.arraycopy操作是操作的本数组,那么元素前移后,数组的最后一个元素和倒数第二个元素是相等的!所以此时还需要调用elementData[--size]=null,将最后一个元素置空。这时候同学们又会想到,那arrayList的容量capacity有没有-1呢?答案是没有,在arrayList中完全没有元素后,也就是size=0时,GC会自动对它进行回收【重点】
2、删除对象remove(Object):
2.1、首先判断object是否为null,在list中是允许元素为null的(其实capacity>size的部分,元素值都null),如果为空的话,就循环查找list的第一个null元素,删除并返回true(循环的条件是index
首先fastRemove和remove的区别是少了一个rangeCheck下标检查和E oldValue = elementData(index)旧值的取值,因为fastRemove不用返回删除的对象,而remove(index)要返回删除对象。那难道就因为这两个操作造成它快很多吗?答案是的,他确实比remove快一点,但仅仅是一点,他使用fastRemove的真正原因可能是这里的index是已经确认存在的,也就是index是合法的,不会出现越界的情况。(我自己的猜想)
2.2、如果object不为null,还是会循环下标,然后通过equal()函数去找出object对应的下标,再通过fastRemove删除元素
注意:需要循环删除list元素时,不建议使用remove,因为删除一个元素,其他元素会前移,而循环中的index却+1,导致错过了一个元素,甚至会造成越界,在此强烈建议使用Iterator
arrayList内部是使用对象数组进行存储的,所以获取元素就是直接从数组里取出元素
1、调用rangeCheck(index)判断index>=size的情况(这里不使用rangeCheckForAdd的原因和上面一样)
2、return elementData(index)直接返回下标对应的元素
相信大家从上面的增删查就可以看出来,ArrayList适合每次增加、删除元素时都需要复制、移动后面的元素,这个就需要一定的代价,而查询是通过下标直接从数组里面取值,所以ArrayList适合查询,不适合大量的增删,也就是说它不适合结构性的改变
优化:我们知道添加元素时,每次都要去判断是否需要开辟空间,需要的话就调用Arrays.copyOf或 System.arraycopy去扩容,如果大量增加元素,其效率必然降低,所以在预先知道需要多少空间时,最好在实例化ArrayList时就调用构造方法public ArrayList(int)开辟好所需空间
SubList是父级list(这里就是arrayList)的一个视图,从fromIndex(包含),到toIndex(不包含)
这里先介绍集合类的结构性变化:
前面一直说modCount,他有什么作用呢?它代表的就是list的结构是否发生改变
非结构性修改:指的是不涉及list大小的修改,也就是modCount没变,举个例子,修改集合中某个元素,这个元素是一个对象,我只修改了对象中的属性age的值,这个对list来说毫无影响
结构性变化:我删除了list的一个元素或者我增加了一个元素,都会修改modCount,而且size也会相应改变,这就是list的结构性变化
修改:1、subList和arrayList的非结构性改变都会影响到彼此,也就是同时改变。2、但是对于结构性变化,subList的操作会映射到父级arrayList中,也就是subList改变——>arrayList跟着改变。而arrayList的改变不会映射到subList中,反而会使subList失效,产生ConcurrentModificationException异常