ArrayList源码解析
首先,arrayList的底层是数组,在大部分条件下查询快,增删慢(因为涉及到扩容和数组复制)。
//new一个空arrayList
ArrayList<String> strings = new ArrayList<>();
源码分析:
//一个类单继承多实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于默认大小的空实例的共享空数组实例。
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储数组列表元素的数组缓冲区。数组列表的容量是此数组缓冲区的长度。添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展到 DEFAULT_CAPACITY
transient Object[] elementData;
//已存数据长度
private int size;
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}//此构造器将new出来的arraylist数组设置为空数组,size=0
为什么默认的new arraylist容量不是DEFAULT_CAPACITY = 10?
类似与懒加载,当有数据添加进arrayList后容量才会增加。
arrayList的add方法分析
//添加一个数据进来
strings.add("张三");
源码分析:
public boolean add(E e) {
//保持最上层代码的简洁,便于代码阅读理解
ensureCapacityInternal(size + 1);
//这里elementData[0] = e,再0+1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//elementData={},minCapacity=1;
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//这一步是判断数组是不是第一次添加数据,如果是第一次添加数据就会让数组的初始容量增加为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//此时minCapacity的大小为10
if (minCapacity - elementData.length > 0)
//判断是否需要扩容?
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//0
int newCapacity = oldCapacity + (oldCapacity >> 1);//0,此步为1.5倍扩容
if (newCapacity - minCapacity < 0)//0-10,成立
newCapacity = minCapacity;//10
if (newCapacity - MAX_ARRAY_SIZE > 0)//防止数据溢出,此步可忽略
newCapacity = hugeCapacity(minCapacity);
//创建容量为 newCapacity的新数并遍历旧数组,将数据移入新数组当中。
elementData = Arrays.copyOf(elementData, newCapacity);
}
//再添加一个数据
strings.add("李四");
源码分析
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 2
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity //2) {
//此时elementData容量为10
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;//返回2
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//2-10,不走glow方法。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
arrayList的get方法分析
strings.get(1);
源码分析:
public E get(int index) {
//校验,防止索引越界等问题
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
//强转数据类型,因为数组的默认类型为Object
return (E) elementData[index];
}
arrayList的contains方法分析
strings.contains("张三");
源码分析
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
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;
}
为什么要比较两次?一次不行吗?
不行!!!首先,数组是可以为null值的,null.equals会报错!而且==比较引用数据类型是比较的地址值。
arrayList的remove方法分析
strings.remove(1);
源码分析
public E remove(int index) {
rangeCheck(index);//校验
modCount++;
E oldValue = elementData(index);//赋值
int numMoved = size - index - 1;//因为要将删除的数据后面的数据前移,所以需要计算出需要挪动几个数据(注意:数组索引是从0开始的)
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//将原数组删除元素后的数据集体拷贝到原数组要删除的数据位置以及之后位置上,即index+1及之后的数组索引集体前移一个。
elementData[--size] = null; //[size-1]= null,在拷贝操作结束后,最后一个数据位置为空
return oldValue;
}
strings.remove("张三");//remove的方法重载,删掉第一个相等的值
源码分析
public boolean remove(Object o) {
//看有没有相等的值,没有则返回false
//找第一个相等值的索引进行删除
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++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
为什么说大部分情况下arrayList增删慢?
因为arrayList的增删涉及到扩容和数组拷贝。
但如果只是尾删除,则删除不涉及到数组拷贝,此时效率不会较慢。如果新增未触及扩容,则效率不会降低。
arrayList的clear方法分析
strings.clear();
源码分析
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}