ArrayList: 是 java 集合框架中比较常用的数据结构了。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 三个标记接口 RandomAccess、Cloneable、Serializable ,所以ArrayList 是支持快速访问、复制、序列化的。不是线程安全的。
AbstractList : 继承AbstractList 说明是一个集合,可以增删改查。
三个标记接口的含义:
RandomAccess: 支持随机访问(基于下标),为了能够更好地判断集合是ArrayList还是LinkedList,从而能够更好选择更优的遍历方式,提高性能!
Cloneable:支持拷贝。实现Cloneable接口,重新cloine方法,方法内容默认调用父类的方法。
Serializable : 序列化。将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来可以很轻松的传输和存储数据。Serializable是告诉JVM,我不对中给类做序列化,你帮我做好。如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID,默认的serialVersinUID对于class的细节非常敏感,反序列化时可能会导致InvalidClassException这个异常(每次序列化都会重新计算该值)。
ArrayList类中的属性:
private static final long serialVersionUID = 8683452581122892189L;序列化版本号(类文件签名)。如果不写会默认生成,类内容改变会影响签名,会导致序列化失败。
private static final int DEFAULT_CAPACITY = 10;如果实例化时未指定容量,则在初次添加元素时会进行扩容使用此容量作为数组长度
static修饰,所有未指定容量的实例(也未添加元素)共享此数组,两个空的数组有啥区别?就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。空的构造器则初始化为10,有参构造器则按照扩容因子扩容。
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // arrayList真正存放元素的地方,长度大于等于size
private int size;//arrayList中的元素个数
ArrayLisst构造器:
无参构造器,构造一个容量大小为10的空的list集合,但构造器只是给elementData赋值一个空的数组,只有在第一次添加元素的时候,扩容至10。
当使用无参构造器的时候是把DEFAULTCAPACITY_EMPTY_ELEMENTDATA 复制给elementData.当 initialCapacity为0的时候是把EMPTY_ELEMENTDATA赋值给elementData。当initialCapacity大于0的时候,是初始化一个大小为 initalCapacity 的object数组并赋值给 elementData。
将 Collection 转化为数组,数组长度赋值给 size。 如果 size 不为零,则判断 elementData 的 class 类型是否为 ArrayList,不是的话则做一次转换。 如果 size 为零,则把 EMPTY_ELEMENTDATA 赋值给 elementData,相当于new ArrayList(0)。
ArrayList 集合操作:
添加元素--默认尾部添加:
每次添加元素到集合中都会确认容量大小。然后给size +1。判断如果 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA 就取 DEFAULT_CAPACITY 和 minCapacity 的最大值也就是 10。这就是 EMPTY_ELEMENTDATA 与 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的区别所在。同时也验证了上面的说法:使用无参构造函数时是在第一次添加元素时初始化容量为 10 的。
对 modCount 自增1,记录操作次数,如果modCoun的值大于elementData 的长度就进行扩容。第一次添加元素的时候 elementData的长度为0。
涉及到扩容会消耗性能,但如果提前指定容量,会提升性能,可以达到和 linkedList相当。在特定情况下甚至超越。
指定下标添加元素:
rangeCheckForAdd(index);//下标越界检查
ensureCapacityInternal(size + 1); //判断扩容,记录操作数
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
复制插入位置及后面的元素,到后面一格,不是移动,复制完成后。添加的下标位置和下一个位置指向对同一个对象。
elementData[index] = element;//再将元素赋值给该下标
如何扩容:
扩容步骤:
1. 获取elementData 当前长度
2. 默认扩容之原来容量的1.5倍。
3.如果1.5倍容量太小,将我们需要的容量 minCapacity赋值给 newCapacity
4.如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容
5.将原理数组中的数据复制到大小为newCapacity的新数组中,并将新数组赋值给elementData。
remove操作
操作步骤:
public E remove(int index) {
rangeCheck(index); // 判断下标是否越界
modCount++; // 操作次数+1
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) // 判断是否是最后一位,如果不是最后一位,就从index+1的开始往后的所有元素都向前拷贝一位,然后将最后一个元素置空,方便垃圾回收器回收
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //如果是最后一位直接置空。
return oldValue;
}
当我们调用 remove(Object o) 时,会把 o 分为是否为空来分别处理。然后对数组做遍历,找到第一个与 o 对应的下标 index,然后调用 fastRemove 方法,删除下标为 index 的元素。
fastRemove(int index) 方法和 remove(int index) 方法基本全部相同。