注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本。

本博客将从源码角度带领大家学习关于ArrayList的知识。

一ArrayList类的定义:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

从上述代码可以看到:ArrayList继承自AbstractList同时实现了List,RandomAccess,Cloneable与Serializable接口

二ArrayList类一些重要属性:

 private static final int DEFAULT_CAPACITY = 10;

 private static final Object[] EMPTY_ELEMENTDATA = {};

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

 transient Object[] elementData;

 private int size;

从这里可以看到ArrayList全部操作是基于Object[]数据结构的,即ArrayList底层是基于数组实现的,因此具备较好的随机访问的能力

三ArrayList内部实现原理:我们先来看一下ArrayList的构造器

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);
}
} public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} 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 {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

可以看到当用一个集合作为参数去初始化一个ArrayList的时候,其内部处理过程与LinkedList非常类似,即先将集合转化为数组,然后将元素复制到自己内部定义的数组elementData数组中。返回的若不是Object[]将调用Arrays.copyOf方法将其转为Object[]。Arrays.copyOf代码如下:

 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

四ArrayList一些重要的函数:

1add(E e)与add(int index,E element)

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} 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(E e)表示在ArrayList的末尾添加一个元素,而add(int index,E element)表示在index处添加一个元素,其中为了防止当数组中的容量不够的情况,会调用ensureCapacityInternal(size + 1); 函数用来确保向数组中添加元素的时候数组大小始终足够。代码如下;

 private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
}

而在该函数中调用了ensureExplicitCapacity(int minCapacity)函数,我们来看一下其源码:

 private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)// 超出了数组可容纳的长度,则进行动态扩展,即调用grow函数
grow(minCapacity);
}
 private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//将oldCapacity除以2在加上自己,即新的容量为原来的1.5倍
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()添加元素时,add方法先调用ensureCapacityInternal方法增加自身容量(注意此时仅仅是minCapicity的值加1,而数组内存还未扩容),本质上是通过grow()函数来完成的,在调用grow()函数时,先求出原数组的长度记为oldCapacity,然后将该值更新为oldCapacity+oldCapacity>>1即新的容量为原来容量的1.5倍.,将其记为newCapacity,如果扩容后的容量超出了数组可容纳的最大长度MAX_ARRAY_SIZE,则调用hugeCapacity()将其返回值 Integer.MAX_VALUE作为新的容量,然后调用系统的arraycopy函数将原数组的内容复制到新数组中返回。

即add()方法中存在扩容机制,当通过add()方法添加一个元素时,会先调用ensureCapacityInternal方法来确保数组内存始终足够,本质上是通过grow函数来完成的,在grow()函数中会将原数组的长度更改为其原来的1.5倍,如果更改后的数组容量比传入的minCapacity小,则将更改后的容量更新为minCapacity(该值是期望的数组的最小容量,如当已存在1个元素然后添加一个元素则minCapacity的值为2即能容纳添加进去的元素的最小容量,扩容后的值newCapacity在绝大多数情况下比它大,但可能比它小,因为上述扩容机制采用的是扩大为1.5倍,而采用的是整除,如1+1/2=1,而minCapacity=size+1=2,此时newCapacity<minCapacity)

2addAll(Collection<? extends E> c) 与addAll(int index, Collection<? extends E> c)

 public boolean addAll(Collection<? extends E> c) {
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;
} public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

可以看到,addAll()与add()方法几乎完全相同,只不过存在一个将集合转化为数组的过程,这一点与LinkedList非常类似,然后接下来的操作与add操作完全相同,即

调用ensureCapacityInternal扩容,如果插入位置不是Arraylist的末尾,则调用系统的arraycopy函数将索引处及其后的元素后移一位在index处插入该元素。处理完毕后将临时数组a中的元素arraycopy到elementData中。

3Object[] toArray()

 public Object[] toArray() {
return Arrays.copyOf(elementData, size);
} public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

该函数的作用就是返回一个包含list中所有元素的数组,具体实现过程如下:

如果传入数组的长度length小于size,返回一个新的数组,大小为size,类型与传入数组相同;

否则将elementData的元素全部复制到传入的数组a中,并返回a;

若length大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

4void sort(Comparator<? super E> c)

public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}

可以看到该sort函数是基于Comparator<? super E> c接口的,即待排序的元素必须实现Comparator接口,其实不仅仅是ArrayList,java集合框架中的全部集合的排序都是基于

Comparator接口的。

五总结:

1ArrayList的内部是基于Object[]类型的数组实现的,支持下标访问,因此具备较高的随机访问速度。

2当用一个集合作为参数来构造一个ArrayList时,其内部是是先将该集合转化为数组,这一点与LinkedList非常类似,与HashMap一样,ArrayList也存在扩容机制,即调用ensureCapacityInternal扩容

3可以看到ArrayList中的方法都未使用synchronized关键字修饰,即ArrayList是非同步的。

4ArrayList允许重复元素存在,因为在add元素的过程中不存在HashMap中put时判重替换的过程,只是进行简单的插入操作。



04-07 08:45