分析方式:断点调试,一步一步跟代码。(基于openjdk1.8)
先看看ArrayList类定义的一些私有属性
private static final int DEFAULT_CAPACITY = 10;//数组默认大小
private static final Object[] EMPTY_ELEMENTDATA = {};//空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组(这两个空数组有啥区别,后面说)
transient Object[] elementData;//存放list内容的数组 被transient修饰,内容不会被序列化
private int size;//具体的list内容大小 (和数组的大小是有区别的)
1.我们在使用ArrayList的时候,第一步肯定是new
调用ArrayList的构造方法,如下,有三个构造方法
//无参构造方法,将elementData赋值为空数组{}
public ArrayList() {
// Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//接受参数为elementData的大小
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//大于0时直接new一个对应大小的object数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//等于0时,赋值为空数组
// Object[] EMPTY_ELEMENTDATA = {}
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将list转为数组,赋值给elementData
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
//如果参数数组类型不是Object,就转为Object类型,重新赋值给elementData
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果参数List大小为0,将elementData 赋值为{}
this.elementData = EMPTY_ELEMENTDATA;
}
}
EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两个都表示空数组{ },区别就是当你调用无参构造函数时用的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当你传了数组的初始化大小参数,但是值为0时用的EMPTY_ELEMENTDATA。
可以看出ArrayList在没有指定初始化大小时,往里面添加元素才会初始化内部的数组。
2.接下来看list.add()方法,如下,有两个重载方法
public boolean add(E e) {
//检查当前elementData的长度,如果已经小于size+1,就需要扩容
//需要区别size和elementData长度
//size表示list内容大小;elementDeata表示存放内容的数组长度
//也就是说elementDeata的长度必须大于size+1,才能放新的内容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将内容放入数组末尾
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
//检查index下标是否在[0,size]之间
rangeCheckForAdd(index);
//检查elementData还能不能存入内容
//不能就会扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index下标后面的内容后移(比如要将内容放到index=2的位置,那么以前index>=2的内容都需要往后移一位)
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
由上,下面我们看看ensureCapacityInternal(size + 1)和System.arraycopy这两个方法的实现
先看ensureCapacityInternal(size + 1)方法,这个方法也是ArrayList扩容方法
private void ensureCapacityInternal(int minCapacity) {
//先是调用了 calculateCapacity
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//当你在new ArrayList时没传参数或者参数为0,第一次往list里面add内容之前,elementData={}
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//取两个值,较大的那个
//int DEFAULT_CAPACITY = 10
//这里是添加单个元素minCapacity=1,所以会返回DEFAULT_CAPACITY为10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//这时的minCapacity是上个方法返回值10
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//如果是第一次添加元素,这里minCapacity=10 elementData.length=0
//或者 如果数组中已经存放不下内容时,就会发生数组扩容
//具体的扩容逻辑看grow(minCapacity)方法
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//得到久的数组长度
int oldCapacity = elementData.length;
//(oldCapacity >> 1):将oldCapacity化为二进制,再右移一位
//比如oldCapacity=5,5化为二进制为00000101;右移一位就是00000010,化为10进制就是2(简单来说就是除2取整)
//将新数组newCapacity = oldCapacity + oldCapacity * 0.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
//minCapacity=10
//如果小于10,newCapacity=10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE = 2147483639
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//将elementData拷贝到新的elementData[newCapacity]中
elementData = Arrays.copyOf(elementData, newCapacity);
}
//下面我们看看Arrays.copyOf()方法
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
//需要记住这三个参数分别是啥,不然后面会搞混
//original:原始扩容前存放内容的elementData数组
//newLength:需要扩容到的数组大小
//original:原始数组的Class
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)//如果是Object类型数组
? (T[]) new Object[newLength]//new Object[]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);//创建其他类型数组
//创建了新数组,下一步就是将原始数组original中的数组拷贝到新数组中
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
下面我们开始看System.arraycopy()方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
这个方法被native修饰,是一个本地方法(简单来说就是Java定义接口,由c++来实现,为什么要c++实现呢?肯定是因为用c++来实现效率更高啥),具体C++怎么实现了,大家有兴趣可以自己去看看。
我们主要来看这五个参数分别是什么意思,下面说个实例来说明:
public class TestArrayList {
public static void main(String[] args) {
Object[] original = {1,3,5,7,9};
Object[] dest = new Object[10];
testCopy(original, dest);
System.out.println(Arrays.toString(dest));
}
//将original拷贝到dest数组
public static void testCopy(Object[]original,Object[]dest) {
System.arraycopy(original, 0, dest, 0, 2);
}
}
控制台输出结果为:
[1, 3, null, null, null, null, null, null, null, null]
System.arraycopy(original, 0, dest, 0, 2)这五个参数的意思分别是:
1.源数组
2.从源数组的那个位置开始拷贝
3.目标数组
4.从目标数组的那个位置开始存放
5.从源数组拷贝几个。
大家可以将这个类拷贝到自己本地,修改参数印证一下。
对上诉的一些关键点,不知道大家get到没有,做个总结:
下面我们来看其他的一些常用方法的源码:
list.remove(index)
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; // 将size位置内容设置为null
return oldValue;//返回旧值
}
list.set(index,element)
public E set(int index, E element) {
rangeCheck(index);//检查索引范围
E oldValue = elementData(index);
elementData[index] = element;//将数组对应下标设置为新值
return oldValue;
}
list.get(index);
public E get(int index) {
rangeCheck(index);
return elementData(index);//获取数组下标对应的值
}
list.size()
public int size() {
return size;//直接返回size属性
}
总结:对list的操作其实就是对elementData数组的操作,需要注意有System.arraycopy调用的地方,说明当数据量大时,如果还频繁调用会很耗时。