ArrayList类
ArrayList
继承自:AbstractList
实现接口: List, RandomAccess, Cloneable, java.io.Serializable
属性
//实现Serializable接口,生成的序列版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
两个空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
你一定很好奇为什么弄两个??除了名字其他完全一样啊,有必要吗?
先看官方解释:
We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
用来区分当第一次添加元素时,需要扩容的大小。之前自己没注意到这个细节,后边在 add() 操作中判断是否扩容时会有分析。
//ArrayList中实际存储元素的数组,看到这里是不是感觉我写错了?我也搞不懂为什么 elementData 属性设置默认访问权限,莫非是因为有 transient 不序列化的原因?
transient Object[] elementData;
//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//记录当前ArrayList的长度,而不是 elementData 数组的长度,elementData数组会有值为 null 的位置,而且可能会很多。下面也会说到
private int size;
构造方法
当使用无参构造方法时,给 **elementData **数组设置的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
使用有参构造器且传入的值是0时,为 elementData数组设为 EMPTY_ELEMENTDATA
这里也没有本质区别,都是空嘛,下边的扩容操作才是亮点!
//注意没有一个构造方法设置 size 值
public ArrayList(int initialCapacity) {//指定初始化容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;//
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {//使用默认初始化容量 10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
add(E e)
ArrayList 在每次新增元素时,都会首先进行判断是否需要动态扩容。
注意上边的两个构造方法,分两类
- 当使用无参构造器时,把 elementData 数组设置为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA终于派上用场了!在这种情况下进行「首次」 add() 操作时,会一次性把 elementData 数组大小设为 **10 ,到这里扩容完毕。之后的第 2~9 次 add() 操作不会扩容,因为在判断是否需要扩容即执行ensureExplicitCapacity(minCapacity)**函数时,传入的值是3-10,都小于等于执行完第一次 add() 操作后的数组大小10。只有在第十次 add() 操作时才会扩容,此时会扩容为原数组长度的 1.5倍。再之后再进行 add() 操作,会把当前 ArrayList 中的元素数量加一(size+1)和 elementData数组大小比较,若前者大,则继续扩容为当前数组长度的 1.5倍……
- 使用有参构造器时,当进行 add() 时,只是把 size+1和 **elementData.length()**进行比较,若前置大就扩容至 1.5倍。
最后把 **elementData[size]**设置为 传入的值 e,并执行 **size++**操作。
public boolean add(E e) {
//每次添加都会先进行判断
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//使用无参构造器且首次添加元素时,设置最小扩容大小为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//默认初始化容量10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断是否需要扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//操作数+1
modCount++;//(**继承自AbstractList)
//判断 最小扩容容量>数组大小 :
if (minCapacity - elementData.length > 0)
//扩容:
grow(minCapacity);
}
//扩大至原来的1.5倍或者当前 size+1 ,谁大要谁
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
使用无参构造器和传入值为0的有参构造器扩容的**「区别」**
前者是在首次 add() 时,会一下子把 elementData 数组的长度扩大到10,之后在第10次操作时才会扩容。
而后者在执行 add()时,第一次时会扩容为 1,elementData[0]=e;
第二次仍然会扩容至2,elementData[1]=e;
第三次仍然会扩容至3,elementData[2]=e;
第四次仍然会扩容至4,elementData[3]=e;
第五次仍然会扩容至6,elementData[4]=e;
……
扩容次数在前面几次会明显增加
remove(int index)
public E remove(int index) {
//检查角标是否合法:不合法抛异常
rangeCheck(index);
//操作数+1:
modCount++;
//获取当前角标的value:
E oldValue = elementData(index);
//获取需要删除元素 到最后一个元素的长度,也就是删除元素后,后续元素移动的个数;
int numMoved = size - index - 1;
//如果移动元素个数大于0 ,也就是说删除的不是最后一个元素:
if (numMoved > 0)
// 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//size减1,并将最后一个元素置为null
elementData[--size] = null;
//返回被删除的元素:
return oldValue;
}