ArrayList源码分析
ArrayList的常用方法及基本特点
ArrayList其实就是一个可自动扩容的数组。数组使用前都是要设置它的大小的,可是如果不知道数组中应该存放多少元素,就出现了Arraylist。
基本特点:
特点 | 结论 |
---|---|
是否允许空 | 允许 |
是否允许重复数据 | 允许 |
是否有序 | 有序 |
是否线程安全 | 非线程安全 |
常用方法:
方法 | 功能 |
---|---|
public boolean add(E e) | 把元素添加到列表末尾 |
public void add(int index, E element) | 把指定元素添加到指定位置 |
public boolean addAll(Collection c) | 把指定集合添加当前列表后 |
public E get(int index) | 获得指定位置的元素 |
public E remove(int index) | 删除指定位置的元素 |
ArrayList的添加及扩容
ArrayList的添加扩容过程是在一起的,在添加前会检查数组的大小,如果数组不够大的话就去扩容。
添加元素过程:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
/**
EMPTY_ELEMENTDATA是elementData 的初始化{}一个空数组,elementData是数组缓冲器,
ArrayList的元素都放在这个数组缓冲器中。
DEFAULT_CAPACITY是数组列表初始容量大小为10,ArrayList会在第一次添加元素的时候设置数组缓冲
器的初始大小为10.
**/
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
/**
目的是为了扩容,表示当前列表的大小size大于缓冲数组的长度时进行扩容
**/
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);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE 是容器的最大容量,为Integer的最大值减8.
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);
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
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添加到指定位置
public void add(int index, E element) {
//检查添加元素位置是否在列表数组范围
rangeCheckForAdd(index);
//判断当前容器是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
/**
目的是把当前及之后的元素向后移一位,并对当前位置赋值为指定元素。把当前缓存数组中指定位置index及之后的元素复制出来,然后
覆盖到index+1及之后的位置上,这样完成了向后移一位,但是index与index+1位置上的元素是重复的。
**/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//把指定元素添加到指定位置
elementData[index] = element;
size++;
}
ArrayList的移除
public E remove(int index) {
//检查要删除元素指定位置是否超过列表长度
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//移除元素数量
int numMoved = size - index - 1;
/**
目的是把当前位置之后的所有元素向前移动一位, 首先把当前位置之后的所有元素进行复制,即位置为index+1和之后的所有数组元
素,然后覆盖在index位置上和之后的所有元素。但是由于比原来数组少了一个元素而复制的数组在原数组位置上向前移了一位,所以造
成最后一位元素多余。**/
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//减小列表的大小并把最后一位元素设置为null,这样下次GC就可以回收了,
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
细节分析
elementData为什么用transient修饰?
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
private transient Object[] elementData;
被transient修饰的变量是不可被序列化的,可以看到 ArrayList集合是实现了Serializable接口所以是可以对它序列化的,按照字面的理解会觉得elementData缓存数组序列化不了,那么ArrayList里就不会有数据这个序列化接口也就没有了意义。因为elementData扩容的时候缓存数组未必是满的,如果使用add()不断添加元素当元素添加到第11个的时候,程序会对elementData扩容,这时容器中有11个元素,而容器的大小却是15有四个空间是空的;所以使用transient是为了防止在这个时候对整个列表进行不必要的序列化,加快了序列化的速度,减小了序列化之后的文件大小。
因此ArrayList中重写了writeObject方法:
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 array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}