今天放假呆在家里实在无聊,无意翻到之前自己做的笔记,今天整理出来发个博客。主要讲一下ArrayList的源码。
ArrayList的本质
arrayLilst本质是一个数组。它的源码内部各个方法的实现其实是建立在数组的基础上的。所以想要了解ArrayList,就不得不了解数组的特性。以下是数组的特点:
- 内存地址连续,使用之前必须指定数组长度
- 可以通过下标访问的方式来访问数组元素,查询效率高
- 增删操作会给系统带来性能消耗
源码分析
初始操作
在将源码之前,先将源码中定义的一些属性先简单的理一下:
//默认的初始空间大小
private static final int DEFAULT_CAPACITY = 10;
//默认的空的Object数组
transient Object[] elementData
//数组的大小
private int size;
ArrayList内部实现了两个构造方法,一个是无参的构造方法,一个是有参的构造方法。
无参构造
//初始话一个空的数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有参构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果初始时规定了空间大小并且大于0,那么创造一个相应大小的数组
this.elementData = new Object[initialCapacity];‘
} else if (initialCapacity == 0) {
//如果初始的空间大小为0,就直接赋值一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果给的数组的空间大小小于0,直接抛出异常。
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是第一次添加数据,这个时候,需要将size+1与默认的初始化列表长度10进行比较,取最大的。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是第一次添加数据,直接返回size+1
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//用来记录操作次数
// overflow-conscious code
//如果需要的最小数据大小大于目前的数组长度,进行扩容
//在这里第一次操作是在添加第11个元素的时候。因为初始化时,默认开辟一个空间为10的数组
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//记录当前数据长度
int oldCapacity = elementData.length;
//运算新的数组长度。这里是在原数组长度上扩容0.5.也就是说新扩容的数组长度是原数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//将原有的数组复制到另一个指定大小的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
add方法涉及到了两个重要的方法,分别是ensureCapacityInternal()和calculateCapacity().主要作用如下:
- calculateCapacity():主要获取数组长度
- ensureCapacityInternal():对数组进行扩容
- grow():对源数据进行扩容
get方法
获取指定下标元素的值
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
get方法相对add方法而言比较简单。主要时先判断下标是否超过数组实际长度,如果超过,发出越界异常。然后返回相应的元素。
set方法
给指定下标添加元素
public E set(int index, E element) {
//判断下标是否越界
rangeCheck(index);
E oldValue = elementData(index);
//直接更换当前下标的值
elementData[index] = element;
//返回原来的元素
return oldValue;
}
remove方法
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;
}
remove的主要逻辑如下:
- 判断下标是否越界
- modCount++ 主要记录操作次数,用于failFast判断
- 进行平移 System.arraycopy(elementData, index+1, elementData, index)这个方法主要就是为了让index后面的数据往前移动
- 将数组最后一个下标的元素值设为null(提醒:原来这个位置上的数据已经通过第3步向前平移了)
- 返回原数据