多看源码可以吸收更多的知识,也能更让我们掌握深层次的知识,今天就分析一下我们常用的ArrayList列表的源码是怎么样的,是怎么实现的,在何种情况下使用会比较高效。
首先我们分析源码应该一步步进行,从构造方法开始
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
ArrarList是基础数组而封装的一种数据结构,elementData 定义是个对象数组同时它也是不能被序列化的, initialCapacity故名思意就是初始化数组的长度了,首先会判断长度是否大于0,是new 一个以该长度的对象数组,如果是0则将实现定义好的空数组 EMPTY_ELEMENTDATA赋值给对象数组,小于0则抛出异常。
接下来分析 add(int index, E element)方法
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
add方法是在指定位置插入一个对象, rangeCheckForAdd(int)方法是检查index插入是否会数组越界, ensureCapacityInternal(size + 1)是来判断数组的长度是否足够,size+1是最小容量,会先判断我们的对象数组是不是默认的数组,是则跟默认长度10比较,取最大值,然后会调用ensureExplicitCapacity(minCapacity)方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount是一个引用计数的作用,目的是记录ArrayList结构的变化,在我们遍历的时候,ArrayList同样可以修改,这就会出现数据不同步的问题,遍历就会根据modCount的值与遍历时的值不同而抛出异常,从而达到数据同步的效果,grow是一个来确定扩容的大小的方法
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
在grow( int minCapacity )方法中可以看到,数组的最大长度 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,超过这个长度给出
Integer.MAX_VALUE,然后进行数组的复制 Arrays.copyOf(),Arrays.copyOf()内部实现就是会将新建一个对象数组,然后将原数组从0复制新的数组中,长度是 newCapacity。
分析set(int index, E element),这是更新目标位置的值,并且会返回旧对象的值
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
rangeCheck(index)先检查是否会数组越界,如果不会,取到旧数据,然后复制新数据的值并返回旧数据,
分析 remove(int index)这是删除我们列表的指定位置的值并会返回删除的值
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
首先也可以看到,我们每次进行的操作都会先进行一遍数组是否越界的判断,而且如果操作会改变数组的结构就会进行modCount自增,我们可以看到这里也是主要用到了 System.arraycopy(源数组,源数组的复制位置,目标数组,目标数组的复制位置,复制长度)主要就是通过计算出复制的长度 numMoved,然后来进行数组的位移,并且会手动将最后长度复制为null,让GC更快回收。
分析indexOf(Object o)方法
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
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;
}
通过上面我们可以看到,其实比较只会返回最先得到的值的位置,lastIndexOf方法则是从数组的后面进行比较,实现是类似的ArrayList通过内部类ListItr实现了ListIterator接口,而且该对象可以访问所以AbstractList的方法,所以可以调用 AbstractList 的next()方法进行遍历list
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
首先会进行一遍是否有并发问题的检查也就是会用到我们在前面方法经常看到的modCount变量了
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果出现不一致则会抛出 ConcurrentModificationException,同样remove操作也会抛出此异常,因为都会去调用 checkForComodification()方法, expectedModCount是我们在获取迭代器的时候就会复制的变量,所以迭代开始,中途不能去修改ArrayList的结构,不然会出错。
好了,ArrayList的主要分析就差不多到这里了。
从源码的情况看ArrayList主要是基于数组实现的,所以数组的优点它都有,缺点也是,在查询上,因为前后并无关系,所以会查询较快,循环到查询节点即可,但是在增加,删除上,因为要逐一改更改数组变化后位置,会比较消耗内存,所以在增删会慢。
多看源码,提高自己的姿势水平。