ArrayList源码分析(个人理解)
1、先记录下ArrayList的类继承关系图
2、下面记录下ArrayList所实现接口中的三个标签接口,大部分人只熟悉第一个标签接口
//先看下ArrayList的定义形式,实现了四个接口,其中后三个为标签接口public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}
// 标签接口一,大家最熟悉的序列化接口
public interface Serializable {
}
// 标签接口二,随机访问接口,暗示这个类支持快速(通常是常量时间)随机访问
public interface RandomAccess {
}
// 标签接口三,克隆接口
public interface Cloneable {
}
3、再来看下ArrayList的属性有哪些
// 默认初始化容量,若是无参构造的ArrayList,在添加第一个元素时候会将容量膨胀到下面大小 private static final int DEFAULT_CAPACITY = 10;
// 这个元素数组是静态的,那么全局唯一资源,在指定容量为0时或者其他几个场景会让elementData赋值为此数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 这个数组按照我个人理解,类似一个标记数组,当无参化构造了ArrayList后,会将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
//在调用ArrayList的add方法添加第一个元素时候会判断element是不是和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,
//若是,就尝试将数组快速膨胀到DEFAULT_CAPACITY容量大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 这个是实际存储元素的数组
transient Object[] elementData; // non-private to simplify nested class access
// 这个字段是实际元素的个数
private int size;
// 最大数组大小是2147483647 - 8 个
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
4、接下来看看ArrayList的构造函数们
// 构造方法一,无参构造方法,将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATApublic ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//为什么只有在无参构造函数时才将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA还暂未理解,
//猜想可能是带参构造函数里面的容量不能随意扩容,以防改变使用者原意。
}
// 构造方法二,带指定容量参数的构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 在这里若容量指定为0的时候,把elementData赋值为EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 构造函数三,带集合参数的构造方法
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 {
// 在这里若集合参数内容为空,则让elementData也指向EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
5、ArrayList 函数分析,先从常用的函数分析
5.1 add(E e)
// 先分析add方法,add方法将给定元素添加到elementData末尾,返回值为boolean类型public boolean add(E e) {
// 第一步先验证容量和统计修改次数
ensureCapacityInternal(size + 1); // Increments modCount!!
// 末尾添加给定值,size自增1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 这里有两个函数调用,下面逐一分析
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 在这一步可以看出,若elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,
//那么在添加第一个元素时候minCapacity就是1,肯定返回 DEFAULT_CAPACITY
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 修改次数加1
modCount++;
// 当minCapacity大于实际数组长度时候扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容代码
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新容量为实际元素长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的容量还小于minCapacity,那就让新容量等于minCapacity,如果minCapacity值溢出上界,
//那么下面这个判断就不会执行。还有好几种情况如两个都溢出等等。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 最后将调用Arrays.copy将生成一个新容量大小的数组,并将原来数组值放入,底层调用System.copy(src,srcPos,des,desPos,length)方法
elementData = Arrays.copyOf(elementData, newCapacity);
}
5.2 add(int index, E element)
// 在指定索引位置添加元素public void add(int index, E element) {
rangeCheckForAdd(index);
// 容量检查,在上面已经分析过
ensureCapacityInternal(size + 1); // Increments modCount!!
// 这段就不多说了,将index位置及以后所有元素统一向后挪一个位置
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
// add方法的范围检查
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
5.3 addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c)
// 将一个集合所有元素添加到ArrayList中public boolean addAll(Collection<? extends E> c) {
// 调用collection的通用方法toArray将集合转为数组
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;
}
5.4 get(int index)
// 第二个比较常用的方法就是获取元素的方法getpublic 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];
}
5.5 contains(Object o)
// 个人感觉第三常用的方法就是用contains判断一个元素是否存在数组中public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//这个方法是找元素在数组中首次出现的索引,被contains使用
public int indexOf(Object o) {
// 如果给定值为null,那么遍历数组元素看哪个元素的值为null
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 若给定值 != null 遍历数组逐个对比
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
// 没找到返回-1
return -1;
}
// 这个是查找元素最后一次出现的索引值,这个就是从数组最后面的元素向前遍历
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
5.6 remove(int index)
// 下来比较常用的方法就是remove方法,移除指定位置的元素public E remove(int index) {
// index范围检查
rangeCheck(index);
// 修改次数自增一
modCount++;
// 获取要移除索引位置的元素
E oldValue = elementData(index);
// 总共要移动的元素的数量
int numMoved = size - index - 1;
// 如果要移动的数量大于0,那么index之后的所有元素向前移一个位置
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
// 返回要移除的元素
return oldValue;
}
// 这个方法是移除指定元素,因为要判断数组里面是否存在指定元素,所以算法复杂度O(n)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 先找到指定元素的索引,如果找到索引位置了,那么移除时候就不需要索引范围检查,所以叫fastRemove
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; // clear to let GC do its work
}
以上就是ArrayList的主要方法,在ArrayList内部还有一些不常用的方法和内部类。现在可以看出ArrayList内部基于一个对象数组存放元素,因此拥有数组的特性: 定位快,但是查找需要O(n)复杂度,删除需要挪动的元素较多。
以上是 ArrayList源码分析(个人理解) 的全部内容, 来源链接: utcz.com/z/513767.html