最近在学习Java中集合框架的底层源码,学习这些高人写出来的东西你才有可能成为高人,今天给大家带来的是ArrayList相关方法的源码解读;
通过查看源码我们得知,ArrayList的底层是由一个数组来实现的,也就是this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;代码。在创建一个ArrayList的对象的时候同时创建一个空的数组.DEFAULTCAPACITY_EMPTY_ELEMENTDATA,可以看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA在源码中的定义为一个空的数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};所以当我们走无参构造来创建一个ArrayList对象的时候,数组的大小默认是0
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
//创建一个空的数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
//可以看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA在源码中的定义为一个空的数组
//private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//所以当我们走无参构造来创建一个ArrayList对象的时候,数组的大小默认是0
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
添加一个元素(调用 add() 方法时)的时间复杂度最好情况为 O(1),最坏情况为 O(n)。
arrayList.add("a");
可以通过 add() 方法向 ArrayList 中添加一个元素。
看一下add 方法到底执行了哪些操作。首先是add方法的源码。modCount属性是用来标记当前arrayList对象被修改的次数,修改是指那些改变列表大小的修改,我们的elementData数组默认大小为0,此时当我们添加一个新的元素的时候,就改变了List的大小,所以在add方法中modCount的值会递增;同样的道理在删除一个元素的时候也会进行modCount++操作;
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
//参数 e 为要添加的元素,此时的值为“a”,size 为 ArrayList 的长度,此时为 0。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
可以看到里面又调用了一个add的重载方法,接下来看一下add方法真正的执行:
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
//首先拿当前数组的大小和elementData的长度作比较,如果一样大说明我们的数组已经存满对象了,
就要执行扩容操作;如果没有满则直接将该元素存入当前索引为数组大小的位置
if (s == elementData.length)
//如果需要增加容量,则调用 grow 方法,就是ArrayList执行扩容操作的方法
elementData = grow();
elementData[s] = e;
size = s + 1;
}
下面来看grow方法源码:
这里指定Arrays.copyof方法为了将当前数组复制到一个新数组中,长度为 minCapacity
/**
* 扩容 ArrayList 的方法,确保能够容纳指定容量的元素
* @param minCapacity 指定容量的最小值
*/
private int newCapacity(int minCapacity) {
// 检查是否会导致溢出,oldCapacity 为当前数组长度
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);// 扩容至原来的1.5倍
if (newCapacity - minCapacity <= 0) {// 如果还是小于指定容量的最小值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// 使用 DEFAULT_CAPACITY 和指定容量的最小值中的较大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
// 如果超出了数组的最大长度
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
//扩容至数组的最大长度
: hugeCapacity(minCapacity);
}
以上内容就是ArrayList添加元素的全部操作;
/**
* 用指定元素替换指定位置的元素。
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 先前在指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常
*/
public E set(int index, E element) {
// 检查索引是否越界
Objects.checkIndex(index, size);
// 获取原来在指定位置上的元素
E oldValue = elementData(index);
// 将新元素替换到指定位置上
elementData[index] = element;
// 返回原来在指定位置上的元素
return oldValue;
}
该方法会先对指定的下标进行检查,看是否越界,然后替换新值并返回旧值。
remove(int index) 方法用于删除指定下标位置上的元素,remove(Object o) 方法用于删除指定值的元素。
先来看 remove(int index) 方法的源码:
/**
* 删除指定位置的元素。
* @param index 要删除的元素的索引
* @return 先前在指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常
*/
public E remove(int index) {
// 检查索引是否越界
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];//获取要删除的元素
//执行删除操作;
/**
* 如果移除的是列表末尾的那个元素,就直接用null填补此位置。
* 移除其他位置的元素都需要通过数组拷贝来实现元素位置移动
*/
fastRemove(es, index);
return oldValue;
}
接下来看remove(Object o) 方法的源码
public boolean remove(Object o) {
//获取当前数组
final Object[] es = elementData;
//获取当前数组大小
final int size = this.size;
//下面要使用i的值来查找当前o这个元素第一次在数组中出现的位置,默认从索引0处开始查找
int i = 0;
found: {
// 如果要删除的元素是 null
if (o == null) {
//遍历列表
for (; i < size; i++)
// 如果找到了 null 元素
if (es[i] == null)
//跳出found代码块
break found;
} else {
//遍历列表
for (; i < size; i++)
//找数组中谁和o的值相等
if (o.equals(es[i]))
//如果找到了要删除的元素,跳出found代码块
break found;
}
return false;
}
//调用 fastRemove 方法快速删除元素
fastRemove(es, i);
return true;
}
该方法通过遍历的方式找到要删除的元素,null 的时候使用 == 操作符判断,非 null 的时候使用 equals() 方法,然后调用 fastRemove() 方法。
继续看fastRemove方法的源码
/**
* 快速删除指定位置的元素。
* @param index 要删除的元素的索引
*/
private void fastRemove(int index) {
int numMoved = size - index - 1; // 计算需要移动的元素个数
if (numMoved > 0) // 如果需要移动元素,就用 System.arraycopy 方法实现
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 将数组末尾的元素置为 null,让 GC 回收该元素占用的空间
}
/**
* 返回指定元素在列表中第一次出现的位置。
* 如果列表不包含该元素,则返回 -1。
* @param o 要查找的元素
* @return 指定元素在列表中第一次出现的位置;如果列表不包含该元素,则返回 -1
*/
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
// 如果要查找的元素是 null
if (o == null) {
//遍历列表
for (int i = start; i < end; i++) {
//如果找到了 null 元素
if (es[i] == null) {
// 返回元素的索引
return i;
}
}
// 如果要查找的元素不是 null
} else {
// 遍历列表
for (int i = start; i < end; i++) {
// 如果找到了要查找的元素
if (o.equals(es[i])) {
// 返回元素的索引
return i;
}
}
}
// 如果找不到要查找的元素,则返回 -1
return -1;
}
至于lastIndexOf,其源码实现就和indexOf很相像的,不同的是在遍历列表的时候一个是从头到尾一个是从尾巴开始到头;
public int lastIndexOf(Object o) {
return lastIndexOfRange(o, 0, size);
}
int lastIndexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {// 如果要查找的元素是 null
for (int i = end - 1; i >= start; i--) {// 从后往前遍历列表
if (es[i] == null) {// 如果找到了 null 元素
return i;// 返回元素的索引
}
}
} else {
for (int i = end - 1; i >= start; i--) {// 从后往前遍历列表
if (o.equals(es[i])) {如果找到了要查找的元素
return i;// 返回元素的索引
}
}
}
return -1;// 如果找不到要查找的元素,则返回 -1
}
ArrayList,又名动态数组,也就是可增长的数组,可调整大小的数组。动态数组克服了静态数组的限制,静态数组的容量是固定的,只能在首次创建的时候指定。而动态数组会随着元素的增加自动调整大小,更符合实际的开发需求。
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏哦。