一、属性
private static final int DEFAULT_CAPACITY = 10; 初始容量10
private int size; // 动态数组的实际大小
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //默认的最大容量
ps:MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,是因为有些虚拟机会预留数组的头部信息,一般数组头部是8位,所以减8;是为了避免一些虚拟机内存溢出的问题;
看源码上面的注解大概也是这个意思;有不对的欢迎指出!
二、构造函数
1、有参构造函数
a、设置初始容量
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//初始化数组的容量,定义后内存就会开辟出容量大小的内存空间
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//初始化容量大小为0,只是把当前数组设置为空对象;相当于直接new ArrayList(),不指定初始大小
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
b、设置集合
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//入参是不为空的集合
if ((size = elementData.length) != 0) {
//如果入参c.toArray()不是Object[]类型
if (elementData.getClass() != Object[].class)
//将入参集合以object类型复制到当前数组中
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
解释:elementData.getClass() != Object[].class:因为toArray方法会被子类重写,那么返回的类型就可能不是Object[]类型,就会使该等式成立
原因:将入参集合转为Object对象,是为了可以避免父类转子类的问题出现,可以参考下面的列子
父类转子类的列子:
//解释new ArrayList(Collection<? extends E> c)中elementData.getClass() != Object[].class)的实现原理
ArrayList objectArray = new ArrayList(Arrays.asList("a","b"));
//Arrays.asList("a","b").toArray().getClass() 就不是object类型
System.out.println(Arrays.asList("a","b").toArray().getClass());
objectArray.add("a");
objectArray.add(new Object());
System.out.println(objectArray.toArray().getClass());
如果arrayList内部没有将存储元素转为Object类型的逻辑,那么objectArray.add(new Object()) 这行代码就会报错;因为Object类型转为String类型相当于父类转子类,是不合理的;
2、无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
给elementData 复制为一个空数组
ps: size():获取的是实际的使用的大小
length:数组的容量大小
三、add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断是否需要扩容 size + 1:加了元素后的长度
elementData[size++] = e; //在数组末尾(下标size)加上新元素后,原size+1
return true;
}
看一下容量大小校验的方法:ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity: 这个方法主要是判断当前集合如果是空集合(new ArrayList()),在默认容量10和当前新增元素后容量(minCapacity)之前取最大值返回;不是空集合则返回minCapacity;
ensureExplicitCapacity:
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //modCount当前集合修改次数加1
if (minCapacity - elementData.length > 0) //@1
grow(minCapacity); //扩容方法
}
@1: 如果新增元素后的实际长度minCapacity 大于当前数据的实际长度,则进行扩容
注意: 如果是通过new ArrayList()初始化数组,那么第一次添加元素时,取得的minCapacity就是10且实际数组长度为0,满足minCapacity > elementData.length需要进行扩容
grow:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //按1.5倍扩容
//如果扩容后的容量小于minCapacity,则新容量为minCapacity
if (newCapacity - minCapacity < 0)
//当new ArrayList()创建对象时就会出现这种情况,上述有详细说明
newCapacity = minCapacity;
//如果新容量大于最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
//判断minCapacity是否小于0,小于则报错;minCapacity大于MAX_ARRAY_SIZE则取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity); //copyOf这个方法很耗性能
}
四、remove方法
public E remove(int index) {
//校验下标是否在实际长度size以内
rangeCheck(index);
//修改记录数加一
modCount++;
//获取该下标的值
E oldValue = elementData(index);
//获取需要移动的次数
int numMoved = size - index - 1;
//如果删除的不是最后一个下标,则都会触发这个条件
if (numMoved > 0)
//将需要删除的下标,后面的所有元素依次往前挪一个位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个下标的元素设置为null,且实际长度size减一
elementData[--size] = null; //由gc回收
return oldValue;
}
ps:删除元素时,采用设置null的方法,可以让gc来回收,提高性能;
remove(Objetc o):核心方法fastRemove(index)和上面一致,也是通过移位和设置null来实现的