ArrayList是一个采用类型参数的泛型类,称之为泛型数组列表,相对于普通数组而言最大的特点就是改变了普通数组长度固定的缺点。ArrayList会初始化一个具有初始容量的list,默认的初始容量为10,但ArrayList也提供有参构造来指定初始化容量。一般情况下在我们大致确定集合容量时,建议采用有参构造指定初始容量,避免添加元素时因容量问题引起的频繁的扩容操作带来效率低下的问题。ArrayList常用操作如下:
方法名 | 作用 |
---|---|
ArrayList() | 构建一个默认容量的数组列表 |
ArrayList(int initialCapacity) | 构建指定默认容量的数组列表 |
boolean add(E e) | 在数组列表尾端添加一个元素,添加成功返回为true |
int size() | 返回存储在数组列表中的当前元素数量 |
public void ensureCapacity(int minCapacity) | 重新确定数组列表的容量 |
void trimToSize() | 将数组列表中的存储容量削减到当前尺寸,建议确定不再添加元素之后,使用此方法减少不必要的空间浪费 |
E set(int index, E element) | 设置数组列表中指定位置i元素为element,会将原来的元素覆盖掉 |
E get(int index) | 获取指定位置处的元素 |
E remove(int index) | 删除指定位置元素,index之后元素向前移动一位,返回被删除的元素值 |
void add(int index, E e) | 指定位置插入指定元素,index之后的元素向后移动一位 |
在ArrayList的set和get以及add方法中尤为体现泛型的重要性,在没有泛型之前,它们只能传入或者返回参数为Object类型,而在进行处理的时候,只能进行强制类型转换,并且add和set方法允许接受任意类型的对象,例如存储员工类对象的集合ArrayList突然插入Integer对象
employee.set(i,123);
在编译时编译器不会给出任何警告,而只有在检索对象并试图对它进行类型转换时才会发现有问题。而我们使用ArrayList时,编译器就会及时检查到这个错误的存在。
ArrayList底层仍然采用数组来实现,所以它也具有数组的部分特点,例如在遍历或者获取元素时都相对于其它集合类有很大的优势,这也是有序下标索引所带来的便利。而这种有序索引同样也会带来在插入中间元素或者删除中间元素劣势,boolean add(E e)方法可能会相对于set或者其多态方法(如add(int index, E e) )好一点,它只是数组末尾添加元素,至少不会引起元素的大幅度移动,顶多只会引起容量不足所导致的扩容操作。而add(int index, E e)或者remove(int index) 方法都会引起位于i之后的所有元素向后或者向前移动一个位置,并且在容量不足时执行add还可能同时引起扩容操作,对于数据量较大的数组列表的频繁的操作将会对性能造成毁灭性的打击。所以在数组较大且增删操作较为频繁时建议使用链表结构的LinkedList集合。
ArrayList采用的是1.5倍扩容,简单的来说就是将原数组中的元素直接复制到1.5倍长度的新数组上。扩容核心方法如下:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
受到内存的限制,扩容操作也不可能做到无限制的增长,ArrayList以及包括其它集合都具有一定的扩容限度,主要会根据oldCapacity(旧容量)、MAX_ARRAY_SIZE(Integer最大限度-8,数组可用最大容量,-8是因为需要8bit来存储数组的元数据,如数组长度、class等)、minCapacity(最小需要容量,由ArrayList类中的size变量获取,每添加一个元素,size自动加1)来确定newCapacity(新容量),判断步骤如下:
- int newCapacity = oldCapacity + (oldCapacity >> 1);新容量初始化为原容量的1.5倍,但不一定会作为扩容容量;
- 如果newCapacity - minCapacity < 0,也就是新容量小于所需要的最小容量,则新容量初始化为最小所需要的容量(其实这里有一丝困惑,为什么要用减法进行比较,而不直接使用“<”来进行比较,如果采用newCapacity < minCapacity方式比较,就不用再考虑minCapacity的情况了,不抛出OutOfMemoryError溢出异常其实也对扩容操作没有影响,可直接确定容量为MAX_ARRAY_SIZE或者是Integer.MAX_VALUE,求解惑);
- 如果newCapacity - MAX_ARRAY_SIZE > 0,也就是新容量大于数组最大容量,可能有两种情况导致出现新容量大于数组最大容量:1⃣旧容量扩大1.5倍之后导致新容量超过最大限度,此时判断最小所需要的容量是否超过数组最大容量,如果超过了则利用数组元数据空间存储元素,新容量则设值为Integer的最大长度,即Integer.MAX_VALUE,如果没超过新容量则为数组最大可用容量,即MAX_ARRAY_SIZE; 2⃣还有一种情况就是最小所需容量计算错误,导致为负值,两者相减不减反增,导致新容量超过了最大可用容量,此时直接抛出OutOfMemoryError内存溢出异常。
确定新容量之后,调用Arrays中的copyOf方法直接创建新数组列表并将元素依次复制到新数组中即可完成扩容操作。