ArrayList是刚接触java用的最多的一个容器类,它也有很多的特点:
- 自动扩容 实例化一个ArrayList对象以后,可以放任意多的对象进去,而不像数组,使用前必须声明大小,超过这个大小就会抛出数组越界的异常.
- 检索速度快 速度快是相较于链表.链表要找到一个指定对象必须要对整条链进行遍历.而ArrayList只要知道索引就能很快得出结果,不需要费时进行遍历.
ArrayList的原理##
ArrayList是List家族中的一员,继承了AbstractList.依赖于数组帮它存储数据,所以它本质上和数组没有区别,只是数组的升级版本.所以它可以像数组一样可以用索引来查找对象.
那么它是怎样实现自动扩容?当然就要先看看add方法了.
private transient Object[] elementData;//这是帮助ArrayList管理数据的数组对象
private static final int DEFAULT_CAPACITY = 10;//默认的容量为10
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 这个方法中确认是否有必要扩容
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) { //判断elementData对象是否为空
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//取minCapacity和DEFAULT_CAPACITY之间最大的数作为数组的容量
}
ensureExplicitCapacity(minCapacity);//执行下一步方法
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果要求扩充的容量是否大于当前数组的长度
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//原先的容量oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);//将要扩充的容量newCapacity 是原来的1.5倍
if (newCapacity - minCapacity < 0)//如果newCapacity 反而比指定的容量minCapacity要小,那么扩充大小根据minCapacity来定
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity已经超出了最大范围,那么根据minCapacity来判断大小,这一步基本上不用关心,其实就是在扩充至1.5倍和minCapacity大小中选择
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//这是ArrayList为什么能够扩容的原因.
}
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());//拷贝原来的数组对象到一个新的长度为newLength的数组中.可以再进一步查看copyOf方法.
}
//如果数组类型是Object数组,那么就构建一个newLength的Object数组
//如果不是的话就构建一个对应类型的数组对象
//将原来的数组从头开始,按照原生数组长度或者数组对象的最小值,复制到新的数组对象,并返回该对象.
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;
}
- 在add方法中,当它达到能够存储的上限时,它存储的内容就会被复制到另外一个"更大尺寸"的数组中,并被这个新的数组取而代之.而它则被无情地抛弃,伴随着垃圾回收消失在内存中.
- 这其中最关键的就是System.arraycopy(original, srcPos, copy, destPos,length);
- 指定原始的数组original和要存放复制元素的对象copy,从original的srcPos位置开始复制length长度的元素到copy的destPos位置.
除开add,还有更多的方法调用了这个方法
- add(int index, E e) 在指定位置插入对象.将elementData的inxdex开始的元素复制到elementData的index+1的位置,变向将index以后所有的元素后移了一位.这样就能理所当然的让新对象占据index的位置了.System.arraycopy(elementData, index, elementData, index + 1,size - index);
- remove(int index) 判断移除的对象是不是最后一个,如果不是,那么将该索引的右边的对象左移一位,最后去掉尾部重复的对象System.arraycopy(elementData, index+1, elementData, index, numMoved);
- addAll(Collection<? extends E> c) 先得到c的数组对象a,将数组根据a的长度扩容,然后将a拷贝到elementData的尾部System.arraycopy(a, 0, elementData, size, numNew);
- addAll(int index, Collection<? extends E> c) 将c的数组对象拷贝到指定index的位置,如果index不在尾部,还要将elementData内index位置的对象后移System.arraycopy(elementData, index, elementData, index + numNew,numMoved);最后才同样调用相同方法System.arraycopy(a, 0, elementData, size, numNew);