一ArrayList定义
1 实现RandomAccess接口:
这也是一个标记接口,接口中没有任何方法和常量,表名该类支持快速的随机访问。在工具类Collections
中应用二分查找方法时判断了是否实现了该接口
1 int binarySearch(List<? extends Comparable<? super T>> list, T key) {
2 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
3 return Collections.indexedBinarySearch(list, key);
4 else
5 return Collections.iteratorBinarySearch(list, key);
6 }
2 实现Cloneable接口:
浅拷贝可以通过调用Object.clone();但是调用该方法的对象一定要实现该接口,否则会抛出CloneNoSupportException。
3 实现了Serializable接口:
表示可以序列化
4 实现list接口:
list接口是list集合类的上层接口,实现该接口的类都必须实现一些方法
二 字段属性:
//集合的默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素,集合的长度即这个数组的长度
//1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
//2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
//表示集合的长度
private int size;
三 构造函数:
此无参构造函数将创建一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。
注意:根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0.
创建一个指定大小的数组,等于0时,就创建一个空数组,小于0时,抛出IllegalArgumentException异常。
将已有的集合复制到ArrayList中。
四 方法:
通过前面的字段属性和构造函数,我们知道 ArrayList 集合是由数组构成的,那么向 ArrayList 中添加元素,也就是向数组赋值。我们知道一个数组的声明是能确定大小的,而使用 ArrayList 时,好像是能添加任意多个元素,这就涉及到数组的扩容。
扩容的核心方法就是调用Arrays.copyOf 方法,创建一个更大的数组,然后将原数组元素拷贝过去即可。下面我们看看具体实现。
如上图所示,增加元素之前会调用ensureCapacityInternal()方法来确定集合的大小,如果集合满了,就要扩容操作。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//这里的minCapacity 是集合当前的大小+1,可以看add的传参
private void ensureCapacityInternal(int minCapacity) {
//elementData 是实际用来存储元素的数组,注意数组的大小和集合的大小不是相等的,前面的size是指集合大小
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
接下来就是扩容的方法:
1 private void grow(int minCapacity) {
2 int oldCapacity = elementData.length;//得到原始数组的长度
3 int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组的长度等于原数组长度的1.5倍
4 if (newCapacity - minCapacity < 0)//当新数组长度仍然比minCapacity小,则为保证最小长度,新数组等于minCapacity
5 newCapacity = minCapacity;
6 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483639
7 if (newCapacity - MAX_ARRAY_SIZE > 0)//当得到的新数组长度比 MAX_ARRAY_SIZE 大时,调用 hugeCapacity 处理大数组
8 newCapacity = hugeCapacity(minCapacity);
9 //调用 Arrays.copyOf 将原数组拷贝到一个大小为newCapacity的新数组(注意是拷贝引用)
10 elementData = Arrays.copyOf(elementData, newCapacity);
11 }
12
13 private static int hugeCapacity(int minCapacity) {
14 if (minCapacity < 0) //
15 throw new OutOfMemoryError();
16 return (minCapacity > MAX_ARRAY_SIZE) ? //minCapacity > MAX_ARRAY_SIZE,则新数组大小为Integer.MAX_VALUE
17 Integer.MAX_VALUE :
18 MAX_ARRAY_SIZE;
19 }
对于 ArrayList 集合添加元素,我们总结一下:
①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
④、第 Integer.MAX_VALUE - 8 = 2147483639,然后 2147483639%1.5=1431655759(这个数是要进行扩容) 次添加元素,为了防止溢出,此时会直接创建一个 1431655759+1 大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。
⑤、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
⑥、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:能向集合中添加 null 的,因为数组可以有 null 值存在。
删除元素:
- 根据给定索引删除指定元素:
/**
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常
modCount++;
E oldValue = elementData(index);//得到索引处的删除元素
int numMoved = size - index - 1;
if (numMoved > 0) //size-index-1 > 0 表示 0<= index < (size-1),即索引不是最后一个元素
//通过 System.arraycopy()将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work 将数组最后一个元素置为 null,便于垃圾回收
return oldValue; // 将被删除的元素返回
}
2.根据元素值删除:
/**
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) { //用if判断了一次 就直接返回,所以remove是删除第一次出现的元素
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
修改元素:
修改元素就比较简单,先调用rangeCheck判断给定索引是否超出范围,再替换索引出元素,返回被替换的值。
/**
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
获得元素:
根据索引获得元素。
根据元素获得索引,还有 lastIndexOf(Object o) 方法是返回最后一次出现该元素的下标。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null) //依然是用的if判断就直接返回,所以也是获得第一次出现该元素的索引
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1; //如果没有,就返回-1;
}