最近在阅读容器的源码,因此也想找个地方总结一下分析成果,抛砖引玉,如有错误,还望指正。
文章目录
1. 简述
ArrayList是一个实现list接口,长度可变的,非同步的、可以允许null元素的数组。
该类所有的操作耗费的时间都是线性的,O(n)。
因为所有的操作都是非同步的,因此多线程的情况下,增加元素、包括自动的扩容都是会有安全问题,为了解决线程安全问题,jdk也提供了相应的工具
List list = Collections.synchronizedList(new ArrayList(...));
2. 类的继承关系
首先看一下该类主要的继承和实现关系:
粗虚线表示抽象类,细虚线表示接口。
3. 相关字段
-
默认容器大小
private static final int DEFAULT_CAPACITY = 10
-
空数组的实例
private static final Object[] EMPTY_ELEMENTDATA = {}
-
默认大小的空数组实例,与
EMPTY_ELEMENTDATA
不同,在添加原数组时会自动扩容
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
-
用于存放数据,当空数组中存放了一个数组,则容器的默认大小则置为10
transient Object[] elementData;
-
数组中实际存放数据的大小
private int size;
4. 构造方法
创建一个指定大小的list
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);
}
}
创造一个默认大小的容器。容器默认大小为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以在构造器中默认传入一个集合
/**
* 1. 将传入的集合转为数组
* 2. 如果该数组大小不为0,则将原数组拷贝到Object数组中,
* 3. 如果数组大小为0,则直接创建一个空数组
* @param c
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
5. 常见方法讲解
1. add、addAll
- 将元素添加到数组的尾部
/**
* 1. 在真正的添加元素之前,首先确定是否需要添加扩容
* 2. 添加元素
* @param e
* @return
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 将元素查到数组的指定位置
/**
* 1. 确认下标合法
* 2. 检查是否需要扩容
* 3. 将index开始的数组整体往后移
* 4. 插入真正的元素
* @param index
* @param element
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
- 将集合c添加到原list后面
/**
* 将集合c追加到原数组的末尾,内部的原理是通过复制的方式实现。
* @param c
* @return
*/
public boolean addAll(Collection<? extends E> c) {
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;
}
2. remove/clear
- 删除指定位置的元素
/**
* 1. 检查下标是否合法
* 2. 获取被删除的元素
* 3. 通过arraycopy复制方式删除元素
* 4. 释放最末尾的元素,节约内存
* @param index
* @return
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
- 删除指定的元素
/**
* 目的:删除列表中第一次出现o的对象
* 1. 判断o是否为null,如果为null,则通过循环删除null
* 2. 不为空,则删除对应的下标。
* @param o
* @return
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
- 删除所有的元素
/**
* 删除数组中所有的元素
* 具体实现:将每个下标对应的数据设置为null
*/
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)的数据
* @param fromIndex
* @param toIndex
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - 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;
}
size = newSize;
}
3. 扩容机制
可以在前面的添加元素中看到,每次添加元素之前,必须检查list是否有足够的容量容纳对象。如果不够,则需要扩容。
- 保证数组最小容量
- 计算容器大小
- 确定扩容逻辑
- 具体的扩容操作
/**
* 保证数组最小容量
* 1. 计算容量
* @param minCapacity
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算容器大小
* 1. 判断现存数据默认数组数据是否相同,如果相同,则在默认的大小和minCapacity取最大值
* 2. 如果不相同,则直接返回minCapacity
* @param elementData
* @param minCapacity
* @return
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 确认是否需要扩充容量
* modCount为父类的定义的元素,用于统计扩容的次数,当真正数组的最小长度大于数组容量,则需要扩容。
* @param minCapacity
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 扩充数组的容量,确保数组最少能够容纳minCapacity个元素
* 1. 确定旧list的容量
* 2. 每次都两倍的扩容。比如初始大小为10,第二次扩容则为10+10/5 = 15,每次扩容都是上次的1.5倍
* 3. 通过Arrays.copyOf()方式扩容
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
注意扩容逻辑中的hugeCapacity()函数,其逻辑主要是:
/**
* 扩充最大容量
* @param minCapacity
* @return
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
4. 查找数组大小
/**
* 返回数组的大小
* @return
*/
public int size() {
return size;
}
5. 判断是否为空
/**
* 判断list是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
6. 是否包含某个元素
具体的是通过indexOf()方法寻找对象o的下标。
/**
* 是否包含对象o。如果包含,则返回true;不包含,返回false
* @param o
* @return
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
查找对象o的下标
/**
* 返回第一个等于o对象的下标。如果不包含,则返回-1
* @param o
* @return int
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
7. 将list转化为数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
8. 获取下标index的元素
/**
* 返回下标为index的元素
* 1. 判断下标是否合理
* 2. 通过elementData()获取对应的值
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
通过elementData获取对应的元素
E elementData(int index) {
return (E) elementData[index];
}
9. 替换元素
/*
* 代替下标为index的元素,返回被代替的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
总结
- 在ArrayList中,无论是add、删除元素还是扩容,都大量用到了
System.arraycopy
方式进行数组复制,因此针对于插入、删除操作及扩容操作整体性能较慢。当然这里说的慢指的平均时间复杂度。 - ArrayList和Vector内部的实现逻辑基本一致,不同的是Vector的所有操作都是加了synchronize,都是同步方法,Vector性能整体性能较差。