本文主要分为两部分:
- ArrayList动态修改长度的秘密、集合size和数组length的区别
- ArrayList的数据结构(随机访问、插入、删除)
1. ArrayList动态修改长度的秘密、集合size和数组length的区别
//ArrayList构造函数的源码:(无参构造)
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
//数据容器
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
//可以看出ArrayList其实是一个Object数组,初始化大小为0。
//数组的特性:其容量固定无法修改;数组中无法判断其中实际
//存有多少元素,length只是告诉我们数组的容量。
//那么ArrayList是怎么实现动态修改长度又是怎么知道数据大小的?
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
...
}
//既然动态变化长度,那么从添加和删除数据的方法来看吧。
//先看添加:
//ArrayList中有四个添加数据的方法:
//add(E e)添加数据到集合末尾;
//add(int index, E element)添加数据到指定位置;
//addAll(Collection<? extends E> c)添加集合到该集合末尾;
//addAll(int index, Collection<? extends E> c)添加集合到指定的位置。
//四个方法操作差不多,只需要了解其中一个就可以了,
//看一下ArrayList add(E e)方法的源码:
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE =
Integer.MAX_VALUE - 8;
private int size;
public boolean add(E e) {
//可以看出是在这个方法中确保了容器的大小,
//也就是动态改变容器大小
ensureCapacityInternal(size + 1);
//添加数据到尾部,在这里size++;
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//确认容器大小,默认最小值为10。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//记录修改次数
modCount++;
//判断是否需要扩充数组长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//容量增长一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//下面这个判断一般忽略
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//到这就能看出动态增长实际上是new了一个新的数组,
//然后将原数组数据复制到新数组中,
//然后elementData指向新的数组。
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
//最后再返回看add()方法中,elementData[size++] = e;
//将数据赋值到了末尾,然后size+1。
//在ArrayList中数组容量最小为10,在达到临界值后,增长原数组长度的
//一半大小。size初始化为0,size+=添加的元素个数。
//集合size和数组length的区别:size<=length。
//常用的remove方法有:
//remove(Object o):删除指定的元素;
//remove(int index):删除指定下标的元素:
//removeAll(Collection<?> c):删除集合中的元素。
//主要看一下后两个删除方法吧,第一个第二个内部操作差不多。
public E remove(int index) {
//角标越界
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//修改次数
modCount++;
E oldValue = (E) elementData[index];
//要复制的长度
int numMoved = size - index - 1;
if (numMoved > 0){
//arraycopy(Object src, int srcPos,
// Object dest, int destPos,
// int length);
//src:源数组;srcPos:源数组要复制的起始位置;
//dest:目的数组;destPos:数据放置目的数组的起始位置;
//length:要复制的长度。
//如:int[] ints={0,1,2,3,4,5,6};
//arraycopy(ints,0,ints,3,2);
//output:{0,1,2,0,1,5,6}
System.arraycopy(
elementData, index+1,
elementData, index,
numMoved);
}
//数据长度减一,最后一个数据制空。
//在这里size--
elementData[--size] = null;
return oldValue;
}
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//去掉要删除的,然后整理数组
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//为了兼容 c.contains()的异常。
if (r != size) {
//复制异常以后的数据到数组中
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//制空重组后的数组中不需要的部分。
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;//在这里size等于重组后的数据长度
modified = true;
}
}
return modified;
}
//可以看出,在remove的方法中没有动态修改数组的长度。
//size-=删除的元素个数。
2. ArrayList的数据结构(插入、删除、随机访问)
//ArrayList基于顺序存储结构,适合随机访问元素,
//不适合在List中大量插入和移除元素。
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index,
elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//插入方法中需要将index以后的数据元素往后移动,
//当数据量大的时候,这种插入操作无疑是耗时的。
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(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;
return oldValue;
}
//删除操作需要将index以后的数据元素往后移动,
//和插入一样,当数据量大的时候,这种操作是比较耗时的。
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
//随机访问get方法通过数组下标来返回数据。基于顺序存储结构,
//为一连串的数据存储,通过下标可以直接返回数组中数据。
本文解析了ArrayList如何通过动态调整容量实现长度变化,并介绍了其数据结构特点,包括随机访问、插入和删除操作。
439

被折叠的 条评论
为什么被折叠?



