ArrayList源码理解
导读
与LinkedList源码理解放在一起查阅,效果更好
本文先给出ArrayList的特点,再从源码的角度分析为什么会有这些特点:
- 对队成员变量的分析,可以知道ArrayList的数据结构
- 对add()方法的分析,可以得知ArrayList添加数据的效率不高
- 对get()方法的分析,可以看出ArrayList查询的效率非常高
- 对remove()方法的分析,可以了解到ArrayList删除数据的效率不高
特点
与LinkedList比较,ArrayList有以下特点:
- 查询效率高
- 添加和删除的效率不高
- 数据结构是数组
源码分析
成员变量
从成员变量中可以看出,ArrayList是用数组Object[]来储存数据的
//容器默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的容器
private static final Object[] EMPTY_ELEMENTDATA = {};
//容器,用来储存数据
private transient Object[] elementData;
//标记容器的大小
private int size;
构造方法
三个重写的构造方法都是用来初始化容器
//构造方法
public ArrayList(int initialCapacity) {
super();
//非法参数校验
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//给定数组的大小
this.elementData = new Object[initialCapacity];
}
//构造方法
public ArrayList() {
super();
//默认为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
//构造方法
public ArrayList(Collection<? extends E> c) {
//将给定的集合转换成数组
elementData = c.toArray();
//此时,容器内有数据,更新size
size = elementData.length;
//c.toArray()出现异常的处理
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
add()的分析
从下面的代码中可以看出,ArrayList是通过数组赋值的方式来添加数据
//添加数据
public boolean add(E e) {
//更新一些数据(modCount),根据一些情况来扩充容器的大小
ensureCapacityInternal(size + 1);
//添加数据,更新size
elementData[size++] = e;
return true;
}
//向指定的位置添加数据
public void add(int index, E element) {
//非法参数校验
rangeCheckForAdd(index);
//更新一些数据(modCount),根据一些情况来扩充容器的大小
ensureCapacityInternal(size + 1);
//从index开始(包括),数据向后移动一位(这里的效率低)
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//添加数据
elementData[index] = element;
//更新size
size++;
}
从下面的代码中可以看出数组的大小是动态变化的,一般扩充倍数为1.5倍
//根据minCapacity来确定容器的大小
private void ensureCapacityInternal(int minCapacity) {
//当前为空容器的处理
if (elementData == EMPTY_ELEMENTDATA) {
//minCapacity取最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//更新modCount
modCount++;
// 如果需要的容量大于当前的容量,扩充容器
if (minCapacity - elementData.length > 0)
//扩中容器的大小
grow(minCapacity);
}
//ARRAY的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//扩中容器
private void grow(int minCapacity) {
// 获取当前容器的大小
int oldCapacity = elementData.length;
//扩充1.5倍
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);
}
//处理容量巨大时的情况
private static int hugeCapacity(int minCapacity) {
//超过int的最大值,抛出异常
if (minCapacity < 0)
throw new OutOfMemoryError();
//根据情况设置为int的最大值或者MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
说明:ArrayList在添加数据的时,有时候会移动大量的数据,导致添加数据效率不高。
get()的分析
从下面的代码中可以看出,ArrayList在查找数据时,通过index直接锁定内存,可以迅速地找到相应的数据
//获取指定位置上的数据
public E get(int index) {
//非法参数校验
rangeCheck(index);
//返回数据
return elementData(index);
}
remove()的分析
移除指定位置上的数据:remove(int index)
//移除指定位置上的数据
public E remove(int index) {
//非法参数校验
rangeCheck(index);
//更新modCount
modCount++;
//获取指定的数据
E oldValue = elementData(index);
//计算要移动数据的个数
int numMoved = size - index - 1;
//根据情况去移动数据
if (numMoved > 0)
//从index+1开始,所有数据向前移动一位,这样,index位置上的数据就被覆盖了,相当于移除了
//这里可以看出,移除操作的效率不高
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//容器的最后一位置空,更新size
elementData[--size] = null;
return oldValue;
}
说明:
1. 移除操作实际上是一个覆盖数据的操作,是将后面一个数据覆盖到前一个位置,如果是末尾的数据,则直接置空。
2. 与add()中的耗时操作类似:可能移动大量的数据导致低效率
移除特定的数据:remove(Object o)
//移除容器中第一个相应的数据
public boolean remove(Object o) {
//首先对o进行空判断,防止o.equal()报错
//两部操作的逻辑一致
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;
}
//移除指定位置上的数据
private void fastRemove(int index) {
//更新modCount
modCount++;
//计算要移动数据的个数
int numMoved = size - index - 1;
//根据情况去移动数据
if (numMoved > 0)
//从index+1开始,所有数据向前移动一位,这样,index位置上的数据就被覆盖了,相当于移除了
//这里可以看出,移除操作的效率不高
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//容器的最后一位置空,更新size
elementData[--size] = null;
}
说明:与remove(int index)相比,多了一步遍历查询的操作
indexOf()的分析
//根据Object找到第一个出现的位置
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;
}
//没有找,返回-1
return -1;
}
这里代码与LinkedList中的类似,都是遍历对比。效率高也仅仅是这里锁定了内存。
结语
了解了ArrayList的原理,对我们优化代码,提升效率会有很大的帮助,这里也给出使用建议:
如果查询操作较多,使用ArrayList的效果更好.
与LinkedList源码理解放在一起阅读,效果更好
转载请标明出处http://blog.youkuaiyun.com/qq_26411333/article/details/51583376#t10