今天在看HashMap源码的时候突然涉及到了ArrayList的源码,现在来回忆下:
ArrayList:
Arraylist是一个实现List的可变数组,可以存储所有元素包括null;在此次源码的分析上总结可以优化的问题是:因为ArrayList是自动增长,随着ArrayList中不断增加元素;自动增长的话需要进行数据的重新拷贝,所以如果可以预知数据量的多少,可以在初始化ArrayList的时候就指定其容量,这样可以减少大量的数据重新拷贝操作,因为默认容量才10;其次也可以在应用程序使用其ensureCapacity成员方法来进行一次性扩容;
ArrayList的属性和构造方法上:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
//缓冲区数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
//指定容量初始化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);
}
}
//默认初始化
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//构造一个包含指定collection的元素的列表
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;
}
}
可以从elementData的变量的注解上JDK给我们的说明:
- 添加第一个元素时,*将扩展为DEFAULT_CAPACITY大小,也就是10。
- ArrayList的容量是此数组缓冲区的长度
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。
1:set(int index, E element)
//
public E set(int index, E element) {
//进行范围判断
rangeCheck(index);
//获取旧值
E oldValue = elementData(index);
//插入新值
elementData[index] = element;
//返回旧值
return oldValue;
}
2:add(E e)
可以看到add添加的元素是在数组列表的尾部
public boolean add(E e) {
//其主要主要是判断长度不够,将其扩容
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
3:add(int index, E element)
指定索引插入元素
- 此索引下无元素直接插入
- 有的话将此索引且后面的元素后移1位后插入,且后移是通过copy 的操作来实现
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
// 如果数组长度不足,将进行扩容。
ensureCapacityInternal(size+1); // Increments modCount!!
// 将 elementData中从Index位置开始、长度为size-index的元素, 拷贝到elementData也就是其自身的 Index+1开始
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//插入元素
elementData[index] = element;
size++;
}
4:addAll(Collection<? extends E> c)
该collection中的所有元素添加到此列表的尾部,且保持其的顺序
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 将 Collection中从索引0位置开始、长度为其length的元素, 拷贝到elementData的索引 size开始,也就是其尾部开始
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
5:addAll(int index, Collection<? extends E> c)
与上面对比,上面是直接默认插入尾部;而此方法可以指定插入的位置
public boolean addAll(int index, Collection<? extends E> c) {
//判断指定位置的合理性
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);
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;
}
ArrayList的读取操作:
get(int index)
返回此列表中指定位置上的元素。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
接下来就是删除操作:1:remove(int index);2:remove(Object o)
1:remove(int index);
可以看到删除指定元素,并不是直接删除,删除完还需要进行元素的移动
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
2:remove(Object o);
通过源码可以看到,分两种情况:传入的object是否为null;
且通过其遍历删除可以指定其删除的是首个出现的元素,且删除后都是通过copy操作进行删除后面的元素向前移位
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 类似remove(int index),移除列表中指定位置上的元素。
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
}
可以看到每当向数组中添加元素时,都会去通过ensureCapacityInternal方法来检查添加后的size+1会不会超出这个数组的长度,如果超出就会进行扩容;
与ensureCapacityInternal(int minCapacity)涉及到的方法源码
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果elementData为空的缓存数组,返回minCapacity与默认大小值DEFAULT_CAPACITY的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//每当对elementData进行修改都会进行增加,采用fastFail机制
modCount++;
//如果传入minCapacity的参数大于原来数组的长度就进行grow方法扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 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;
//>>向右位移1位,整型的话是其大小/2, 也就是newCapacity=1.5*oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果1.5*oldCapacity的比需要传入需要扩容的参数minCapacity进来的还小的话,则直接扩容为minCapacity大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果1.5*oldCapacity比 MAX_ARRAY_SIZE还大的话
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方法中我们可以看到,如果1.5*oldCapacity的比需要传入需要扩容的参数minCapacity进来的还小的话,则直接扩容为minCapacity大小;
从这就可以大概体会到,如果经常需要扩容,代价是很大的,因为扩容需要重新进行数据拷贝;所以如果要在一个arraylist中插入大量的元素,最好可以预知下需要保持元素的多少,在初始化的时候指定容量,减少扩容的发生,或者可以在需要大量添加的时候手动扩容ensureCapacity方法来手动增加ArrayList实例的容量
如果扩容多了呢ArrayList还给我们提供了一个将数组容量调整为实际保存元素的数量的功能的方法:trimToSize
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
最后提一下Fail-Fast机制,我们刚刚在源码中看到了很多次modCount++这个语句
那么为什么要进行modCount++呢?
我们可以从上面修改ArrayList的方法可以很明显的真的每次对ArrayList做修改都会增加modCount这个值,每次对ArrayList做修改都会增加modCount这个值,且在迭代器初始化的时候将modCount赋值给expectedModCount,在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了数组,马上抛出ConcurrentModificationException异常
源码:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
//赋值
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
//判断判断modCount跟expectedModCount是否相等
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}