一、对于ArrayList需要掌握的内容
- ArrayList的创建:即构造器
- 往ArrayList中添加对象:即add(E)方法
- 获取ArrayList中的单个对象:即get(int index)方法
- 删除ArrayList中的对象:即remove(E)方法
- 遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历
- 判断对象是否存在于ArrayList中:contain(E)
基本介绍
- 底层使用数组,同时是动态数组
- 由于数组是 Object 类型的,因此可以添加元素 null,可以添加相同元素
- ArrayList 中的操作是线程不安全的
- 实现了 RandomAccess 接口,即可以使用随机访问,通过索引访问效率更高
1.创建
//默认数组初始容量
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 共享空数组实例,用于默认大小的空实例。
* 我们将其与EMPTY_ELEMENTDATA区分开来,
* 以了解添加第一个元素时应该膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//负责存储元素的数组引用,不参与序列化
transient Object[] elementData;
//线性表长度,即已添加的元素数量,默认是0个元素
private int 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);
}
}
//默认的构造方法,直接将0个元素的默认容量的数组对象赋值给elementData
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//接受一个Collection为参数的构造方法
public ArrayList(Collection<? extends E> c) {
//将集合转化为数组并赋值给elementData,数组的长度就是集合的size
elementData = c.toArray();
//将数组长度赋值给size,并判断是否为0
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//如果size不为0,判断elementData的Class对象是否是Object[]的Class对象
if (elementData.getClass() != Object[].class)
//不是则使用 Arrays.copyOf 创建一个新的数组并赋值给elementData
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果size为0,则直接创建一个空数组对象
this.elementData = EMPTY_ELEMENTDATA;
}
}
2.扩容
//确认数组容量是否能满足实际元素数量
public void ensureCapacity(int minCapacity) {
//如果elementData不是用的默认空数组对象
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0 //如果为true,默认传入0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY; //false的话,使用默认的容量,即存储10个元素
//如果最少需要的元素容量 大于 当前数组的容量
if (minCapacity > minExpand) {
//把最少需要的容量传去,去做数组扩容
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
//如果elementData指向的是默认的空数组对象
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//最小容量与默认容量的最大值赋值给minCapacity
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//把最少需要的容量传过来,在ensureExplicitCapacity中扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//首先修改次数+1
modCount++;
// overflow-conscious code
//如果需要的最小容量 减去 实际的数组容量 大于 0(即实际元素数量大于当前的数组容量)
if (minCapacity - elementData.length > 0)
// 将需要的最小容量传进grow方法中做扩容
grow(minCapacity);
}
//数组的最大容量,最大长度,用的Integer的最大值 - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//这是扩容数组的逻辑实现
private void grow(int minCapacity) {
// overflow-conscious code
//首先拿到当前数组的长度 ,记录到oldCapacity中
int oldCapacity = elementData.length;
//新的newCapacity 是 旧的数组长度 + 旧的数组长度/2,即扩展1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新的容量,还没有实际需要的容量大
if (newCapacity - minCapacity < 0)
//那么新的数组容量,就直接用实际元素需要的容量
newCapacity = minCapacity;
//如果新的数组容量大于上限容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用hugeCapacity方法,拿到一个新的容量
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//将原来的数组,按照新的容量长度,生成一个新的数组对象,并且元素逐个copy到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//要是需要的最小容量小于0,直接抛出OutOfMemoryError(),内存溢出
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果需要的最小容量已经超过上限,仍然把上限作为返回值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
3.增加
在表的尾部添加元素
public boolean add(E e) {
//检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在指定位置添加元素
public void add(int index, E element) {
//检查下标是否合法
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将当前index处及以后的所有元素,均向后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将新的元素插入index位置
elementData[index] = element;
size++;
}
//检查下标是否合法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
添加一个集合
public boolean addAll(Collection<? extends E> c) {
//先把Collection转换为数组,长度就是Collection的实际元素的数量
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//从指定位置添加一个集合
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//计算原数组需要移动的元素数量
int numMoved = size - index;
//将原数组所有要移动的元素,往后移动到index+numNew开始处
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将传入List中的元素添加到elementData,从index下标开始,一共添加numNew个元素
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
4.删除
删除指定下标的元素
public E remove(int index) {
//又是先检查下标是否合法
rangeCheck(index);
//修改次数+1
modCount++;
//拿到要移除的元素对象
E oldValue = elementData(index);
//计算要挪动的元素数量,index之前的元素不用移动,之后的元素都得移动
int numMoved = size - index - 1;
//如果移动元素的数量大于0,开始执行,向前移动元素,逐个的赋值
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//移动完元素之后,还要把原来空出来的元素位置,赋值null,释放内存
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
删除指定元素
public boolean remove(Object o) {
if (o == null) {
//遍历数组找到第一个为null的元素并删除
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//遍历数组找到第一个equals(o)的元素并删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//私有方法,根据下标,移除掉元素,和直接移除指定下标位置的元素相同
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
删除所有元素(清空)
public void clear() {
modCount++;
//遍历数组并置空,内存回收
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
删除一个区间的元素
//删除一个区间的元素,fromIndex是开始下标,toIndex是结束下标
protected void removeRange(int fromIndex, int toIndex) {
//先把修改次数+1
modCount++;
//计算要移动元素的数量
int numMoved = size - toIndex;
//将结束下标处的元素,向开始下标处移动,移动元素的数量是numMoved,显然toIndex下标的元素是
//不移除的
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//计算新的线性表长度
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) { //遍历移除元素后,剩下的空间
elementData[i] = null;// 将其均置为null
}
size = newSize;//线性表长度改为新的元素数量
} //该方法是protected,所以只有继承ArrayList时,才能访问
5.修改
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
6.获取
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
7.遍历iterator()
public Iterator<E> iterator() {
return new Itr();//返回一个内部类对象
}
private class Itr implements Iterator<E> {
//标记位:标记遍历到哪一个元素
int cursor = 0;
//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作
int expectedModCount = modCount;
//检测对象数组是否还有元素
public boolean hasNext() {
//如果cursor==size,说明已经遍历完了,上一次遍历的是最后一个元素
return cursor != size();
}
//获取元素
public E next() {
checkForComodification();//检测在遍历的过程中,是否发生了add、remove操作
try {
E next = get(cursor++);
return next;
} catch (IndexOutOfBoundsException e) {//捕获get(cursor++)方法的IndexOutOfBoundsException
checkForComodification();
throw new NoSuchElementException();
}
}
//检测在遍历的过程中,是否发生了add、remove等操作
final void checkForComodification() {
//发生了add、remove操作,这个我们可以查看add等的源代码,发现会出现modCount++
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
iterator()方法是在AbstractList中实现的,该方法返回AbstractList的一个内部类Itr对象
8.trimToSize()
//将剩余数组空间压缩成实际元素占用的空间
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
三、总结
- ArrayList基于数组方式实现,无容量的限制(会扩容)
- 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
- 线程不安全
- add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
- get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
- remove(Object o)需要遍历数组
- remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
- contains(E)需要遍历数组
参考文章:叫我王员外就行 ArrayList源码分析(JDK1.8)