public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList,实现了List接口,底层结构是一个数组,怎么说呢,由于是一个数组,所以拥有数组的优点和缺点(访问快,增删慢)。而且它有下标,所以可以存放重复的元素。(这个应该是list接口的特点,而set容器由于没有下标,所以不能存放重复的元素。)
它有一个默认容量为10,当它进行添加元素的时候,会先进行判断是否有空间添加元素,如果不够就进行扩容,扩容的大小是原来的1.5倍,(应该是效率比较高吧。对比HashMap中的加载因子为0.75)。它进行扩容的时候,采用的方法是Arrays.copyof,底层是System.arrayof方法。添加元素使用的的方法是System.arrayof方法。
当创建一个新的ArrayList时,它使用的是一个空的数组,直到第一个元素被添加它才会开辟一个默认大小的空间去存储元素。其中默认数组大小为10。
默认初始化容量是10
private static final int DEFAULT_CAPACITY = 10;
用来保存空实例的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
用于空实例,直到第一个元素被添加。为了区别EMPTY_ELEMENTDATA,当第一次添加元素之后,应该扩大多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
存储元素的object数组,当第一个元素被添加时,它会从DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个存储空实例的数组转变为这个数组,它的容量会变为默认的大小即10. 可以从这个变量的设置中看出ArrayList的结构实质为数组。
transient Object[] elementData; // non-private to simplify nested class access
数组已经使用的大小。
private int size;
设置最大能使用的数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造函数 | 内容 |
---|---|
public ArrayList(int initialCapacity) | 设置初始容量,当为0时,创建一个初始容量为10的list,当超过0,则根据初始容量设置的大小创建ArrayList。 |
public ArrayList() | 无参构造将会创建一个初始容量为10的list。 |
public ArrayList(Collection extends E> c) | 设置Collection 作为参数构建ArrayList,它是将Collection 中的这个对象最终转换成ArrayList中存在的object数组 elementData来保存数据 |
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//超出0 ,根据设置的初始容量创建数组的大小
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 为0 ,保存空实例的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
//初始为空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//创建了一个包含colleciton 元素的arraylist 要求返回的是这个collection的迭代
public ArrayList(Collection<? extends E> c) {
//保存这个collection中的数据到elementData
elementData = c.toArray();
//如果这个数组不为空 ,如果这个数组不是object类型,就将其转化为object数组
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//为空,数组替换为保存空实例的空数组。
this.elementData = EMPTY_ELEMENTDATA;
}
}
add方法 | 内容 |
---|---|
public boolean add(E e) | 直接添加元素 |
public void add(int index, E element) | 将元素添加到指定下标。 |
add(E,e)方法运行时会调用ensureCapacityInternal方法判断数组大小是否足够,它会通过calculateCapacity方法去计算所需的最小数组大小。如果数组大小能够满足就继续运行,如果不足,数组就进行扩容。每次扩容的大小是原容量+原容量的1/2 。然后通过Arrays.copyof方法将原数组copy到新数组中。
add(index,element)是指定了要添加的元素值和对应的数组中的下标值来进行添加的。它不仅要判断数组大小是否足够,还要判断下标输入的是否正确,通过rangeCheckForAdd方法。它进行添加的方法不同于add(E,e),它是通过 System.arraycopy这个方法,从一个数组中指定的位置开始,copy到另一个数组中的指定位置,复制指定长度的元素。
不过吧,不论是Arrays.copyof还是System.arraycopy方法,它都涉及到了数组的copy,相对于其他,感觉速率要慢点。
public boolean add(E e) {
//将当前已经存储的大小+1之后,进行判断
ensureCapacityInternal(size + 1); // Increments modCount!!
//将元素添加到数组对应下标位置
elementData[size++] = e;
//返回变量表明添加成功
return true;
}
在添加之前会判断空间是否足够
private void ensureCapacityInternal(int minCapacity) {
//调用方法,其中minCapacity是添加这个元素所需要的最小大小,size+1。
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算空间的方法,传入了所需最小大小和数组
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断数组是否为空数组,如果是第一次添加元素,那么返回默认容量和
//需要的最小容量中最大的。
/*
当数组第一次添加元素的时候,它是会被扩展为默认大小的即10,
所以此时要对所需要的最小容量和默认容量进行比较。返回两者中最大的量以满足需要。*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是第一次添加元素,返回需要的最小空间。
return minCapacity;
}
确保数组空间足够使用,如果不足够,则进行扩容。
private void ensureExplicitCapacity(int minCapacity) {
// 修改结构的次数增加
modCount++;
// overflow-conscious code
//如果此时需要的最小容量超出了数组的容量,就进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容是通过对于数组的copy来实现的。
//进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
//保存当前的大小
int oldCapacity = elementData.length;
//设置扩容的新容量大小为原来的容量+原来容量的1/2,也就是原来容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果此时新容量的大小满足了保存元素需要的最小容量,
//那么新容量将被赋值为需要的最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果此时新容量超出了能够设置的最大数组大小,
//那么新容量是能够使用的最小容量,一般是满足需要的。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// copy原来的elementData到新数组中。
elementData = Arrays.copyOf(elementData, newCapacity);
}
指定下标添加元素到ArrayList中
public void add(int index, E element) {
//做一个健壮性的判断,判断索引值是否超出了已经记录的大小或者是否小于0
rangeCheckForAdd(index);
//同add(E element)相似,判断空间是否足够添加一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
/*
从elementData数组(第一个参数,要进行copy的数组)中的index开始,
copy到elementData(第三个参数,目标数组)中的index+1位置
copy的长度为size-index.
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//为对应下标的元素进行赋值为element。
elementData[index] = element;
//记录数组已存元素的size增加。
size++;
}
//对索引值进行健壮性的判断。
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
//如果小于已存元素的大小或者下标小于0 就抛出异常。
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public E set(int index, E element) {
///对下标进行健壮性的判断
rangeCheck(index);
//获取数组中对应下标的元素,新元素替换旧元素,返回旧元素
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
public E get(int index) {
//对下标进行健壮性的判断,返回对应下标中的元素
rangeCheck(index);
return elementData(index);
}
删除的两种方法 | 内容 |
---|---|
public E remove(int index) | 根据索引删除 |
public boolean remove(Object o) | 根据object进行删除 |
public E remove(int index) {
// 对下标进行健壮性的判断
rangeCheck(index);
//修改结构的次数增加
modCount++;
// 获取数组中对应下标的旧元素
E oldValue = elementData(index);
// size - index -1 获取到数组中要删除元素的个数
int numMoved = size - index - 1;
// 如果这个删除元素的下标大于0
if (numMoved > 0)
/* 从elementData数组中的index+1开始,copy到elementData 中的index元素,
移动的元素的个数是numMoved*/
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//清空elementData数组中的这个下标对应元素
elementData[--size] = null; // clear to let GC do its work
//返回旧元素
return oldValue;
}
通过对象删除元素
public boolean remove(Object o) {
//如果元素为null
if (o == null) {
//遍历(已添加元素的长度)。
for (int index = 0; index < size; index++)
//如果数组中对应下标中的元素值为null
if (elementData[index] == null) {
//就调用fastRemove进行删除,并返回true
fastRemove(index);
return true;
}
} else {
//遍历
for (int index = 0; index < size; index++)
//如果数组中含有相等的对象,就调用fastRemove进行删除,并返回true
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
删除真正使用的方法
rivate void fastRemove(int index) {
//修改结构的次数增加
modCount++;
//设置要删除的长度
int numMoved = size - index - 1;
//如果长度超出0
if (numMoved > 0)
/*通过数组的copy来删除*/
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//清空这个下标
elementData[--size] = null; // clear to let GC do its work
}
#### 4 常见问题
从底层结构来说,ArrayList底层是数组,LinkedList底层是链表。数组是一块连续的内存空间,访问的时候,只需要一个接一个的进行查询就可以,而相对于链表,它在内存中的存放地址并不是固定的,查找的时候需要通过元素的前驱后继来指向上一个,下一个的地址来进行查询。所以ArrayList的增删速度要高于LindList。数组中元素的增删,会造成数组中元素的移动,而链表中依旧是只需要修改前驱和后继指向的元素,所以LinkedList的增删速度要高于ArrayList。
其中有两个变量EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA自己一开始看的时候,并不太明白为什么要设置两个功能差不多的变量,都是用于保存空实例的空数组。以下是看了网上的解释之后了解的。EMPTY_ELEMENTDATA是ArrayList的构造函数中指定了初始容量,不过当这个初始容量为0时,这个ArrayList的数组就被设置为了EMPTY_ELEMENTDATA,ArrayList的无参构造,返回的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
在ArrayList中都是什么时候调用的这两个方法呢
在ArrayList中增删时调用了System.arraycopy方法。(主要是我只看了添加和删除元素)。
扩容时调用了Arrays.copyOf(elementData, newCapacity);
Arrays是集合中的一个工具类,与它相似的还有collectionss。它提供了很多关于不同类型数组的copy方法。这个虽然知道是数组的copy,但是还是不是很清晰,所以又找了源码来瞅瞅。可能还是不是明白,但是了解了解应该是不会错的。于是发现**在Arrays.copy方法的底层,竟然是调用了System.arraycopy方法。**不过具体就看不太懂了。不过找了博客看发现Arrays.copyof和System.arraycopy方法是四种数组copy方法之一(for,arrays.copyof, system.arraycopy,clone),其中system.arraycopy方法是四种中较快的一个。
/*
* @param src 源数组,也就是要被进行copy的数组
* @param srcPos 源数组中开始的下标值
* @param dest the destination array. 目标数组
* @param destPos 目标数组中开始的下标值
* @param length 被copy的元素的长度
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
/*
复制指定的数组,用零来截断或填充(如果必要的话),这样拷贝就具有指定的长度。
对于在原始数组和副本中都有效的所有索引,两个数组将包含相同的值。
对于任何在副本中有效但不是原始的索引,拷贝将包含0。
当且仅当指定长度大于原始数组时,这些索引将存在。 */
/**
@param original 要进行copy的数组
@param newLength 需要copy的长度
*/
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
// copyOf(T[] original, int newLength) 在内部又调用了它
/*
复制指定的数组、截断或填充为空(如果必要),以便复制具有指定的长度。
对于原始数组和副本中有效的所有索引,这两个数组将包含相同的值。
对于副本中有效但不是原始索引,副本将包含null。
如果且仅当指定的长度大于原始数组的长度时,才会存在此类索引。
所得数组为newType类。
@param original 要进行copy的数组
@param newLength 要copy的数组的长度
@param newType 要进行copy的数组中元素的类型
*/
public static <T,U> T[] copyOf(U[] original, int newLength,
Class<? extends T[]> newType) {
// 判断是否是object类型,如果是创建一个newLength长度的object数组,
//如果不是Array创建一个newType类型newLength长度的数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//从original的0下标开始,复制Max.min(original.length,newLength)长度的元素
//到copy数组中(从0 下标开始)。
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}