简介
ArrayList是实现 List 接口的实现类,内部是一个大小可变的数组。
ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。
ArrayList 的好处是可以不用限定容器的大小,它会根据元素的增加而扩容。但是存储进去的数据类型都会变成Object
类型,虽然每个元素有自己的index
,但不像数组的下标可以更加方便的操作。
类定义
ArrayList
定义如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 实现 RandomAccess 接口
这是一个标记接口,一般此标记接口用于 List
实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通过算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。
比如在工具类Collections
中,应用二分查找方法时判断是否实现了RandomAccess
接口。
- 实现 Cloneable 接口
这个类是 java.lang.Cloneable
,前面我们讲解深拷贝和浅拷贝的原理时,我们介绍了浅拷贝可以通过调用 Object.clone()
方法来实现,但是调用该方法的对象必须要实现 Cloneable 接口,否则会抛出 CloneNoSupportException
异常。
Cloneable 和 RandomAccess 接口一样也是一个标记接口,接口内无任何方法体和常量的声明,也就是说如果想克隆对象,必须要实现 Cloneable 接口,表明该类是可以被克隆的。
- 实现 Serializable 接口
也是标记接口,表示能被序列化。
- 实现 List 接口
这个接口是 List 类集合的上层接口,定义了实现该接口的类都必须要实现的一组方法,如下所示,下面我们会对这一系列方法的实现做详细介绍。
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
void clear();
boolean equals(Object o);
int hashCode();
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
字段属性
private static final long serialVersionUID = 8683452581122892189L;
// 集合默认大小
private static final int DEFAULT_CAPACITY = 10;
// Object类型的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 这也是一个空的数组实例,和 EMPTY_ELEMENTDATA 空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 用来存储 ArrayList 集合的元素,集合的长度即这个数组的长度
transient Object[] elementData;
private int size;
构造函数
无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
声明的数组,注意此时初始容量是0
,而不是 10
。
- 注意:根据默认构造函数创建的集合
ArrayList list = new ArrayList();
此时集合长度是0。
初始化集合大小构造函数
当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
传入一个集合的构造函数
当我们想将一个集合转化为ArraList
,可用此构造函数:
- 将已有的集合复制到 ArrayList 集合中去
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray(); // Object[] toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加元素
源码分析
ArrayList 集合是由数组构成的,那么向 ArrayList 中添加元素,也就是向数组赋值。一个数组的声明大小是固定的,而使用 ArrayList 时,能添加任意多个元素,这就涉及到数组的扩容。
扩容的核心方法就是调用Arrays.copyOf
方法,创建一个更大的数组,然后将原数组元素拷贝过去即可。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确定集合的大小,如果集合满了,则要进行扩容操作
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
在 ensureExplicitCapacity
方法中,首先对修改次数modCount
加1,这里的modCount给ArrayList的迭代器使用的,在并发操作被修改时,提供快速失败行为(保证modCount在迭代期间不变,否则抛出ConcurrentModificationException异常,可以查看源码865行)
接着判断minCapacity
是否大于当前ArrayList内部数组长度,大于的话调用grow
方法对内部数组elementData扩容,grow方法代码如下:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
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() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
- 第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
- 第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
- Integer.MAX_VALUE - 8 = 2147483639(21亿多),然后第 2147483639/1.5=1431655759(14亿多,这个数是要进行扩容) 次添加元素,为了防止溢出,此时会直接创建一个 1431655759+1 大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。
- 即第14.31655759亿次添加元素时,它的1.5倍就基本是Integer.MAX_VALUE了,就不能再以1.5倍扩容了,而是每次只扩大一个范围。
- 第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,再进行元素添加。
- 第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:是可以向集合中添加 null 的,因为数组可以有 null 值存在。
Object[] obj = {null,1};
ArrayList list = new ArrayList();
list.add(null);
list.add(1);
System.out.println(list.size()); // 2
删除元素
根据索引删除元素
remove(int index)
方法表示删除索引index
处的元素,首先通过 rangeCheck(index)
方法判断给定索引的范围,超过集合大小则抛出异常;接着通过 System.arraycopy
方法对数组进行自身拷贝
。
源码如下:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
直接删除指定元素
remove(Object o)
方法是删除数组中从前往后第一次出现的该元素。然后通过System.arraycopy
进行数组自身拷贝。
源码如下:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
修改元素
通过调用 set(int index, E element)
方法在指定索引 index 处的元素替换为 element。并返回原数组的元素。
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
通过调用 rangeCheck(index)
来检查索引合法性。
当索引为负数时,会抛出 java.lang.ArrayIndexOutOfBoundsException
异常;当索引大于集合长度时,会抛出 IndexOutOfBoundsException
异常。
private void rangeCheck(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
查找元素
根据索引查找元素
源码:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
根据元素查找索引
返回索引是通过遍历整个数组实现的,且返回第一次出现该元素的下标,如果没有则返回 -1
源码:
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
此外, lastIndexOf(Object o)
方法是返回最后一次出现该元素的下标。
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
可见lastIndexOf(Object o)
方法是从后往前遍历的。
遍历集合
- 内容有点多,见原文
- 通过
get(int index)
for(int i = 0 ; i < list.size() ; i++){
System.out.print(list.get(i)+" ");
}
- 迭代器
iterator
ArrayList<String> list = new ArrayList<>();
// 添加元素的步骤省略...
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.print(str+" ");
}
SubList(start, end)
在 ArrayList 中有这样一个方法:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
作用是返回从 fromIndex(包括)
开始的下标,到 toIndex(不包括)
结束的下标之间的元素视图
。如下:
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List<String> subList = list.subList(0, 1);
for(String str : subList){
System.out.print(str + " "); // a b
}
SubList
类是 ArrayList 中的一个内部类。
注意:返回的是原集合
的视图,也就是说,如果对 subList 出来的集合进行修改或新增操作,那么原始集合也会发生同样的操作。想要独立出来一个集合,解决办法如下:
List<String> subList = new ArrayList<>(list.subList(0, 2));
trimToSize()
该方法用于回收多余的内存。trim是修剪的意思,也就是说一旦我们确定集合不再添加多余的元素之后,调用 trimToSize()
方法会将实现集合的数组大小调整为集合元素的大小。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
注意:该方法会花时间来复制数组元素,所以应该在确定不会添加元素之后再调用。