前言
ArrayList是我们最常使用的数据类型之一,也是面试中最容易问到的集合之一。本篇文章主要通过分析ArrayList的源码,更深入的了解ArrayList,ArrayList在JDK1.8中添加了一些新的方法。
本文基于jdk1.8
ArrayList简介
ArrayList底层是一个数组,它的容量可以动态增长。
ArrayList类结构
可以从上图看出,ArrayList继承AbstractList抽象类,实现了List,Cloneable,Serializable,RandomAccess接口。
ArrayList特点
- ArrayList非线程安全,在多线程中保证线程安全的解决方法:使用
CopyOnWriteArrayList
,Vector
,List list = Collections.synchronizedList(new ArrayList());
。 - ArrayList允许插入null。
- ArrayList中的数据存储是有序的。
- ArrayList中的数据可以重复。
ArrayList提供的API
方法名 | 方法解释 |
---|---|
add(E e) | 将指定的元素添加到ArrayList的末尾。 |
add(int index, E element) | 将指定的元素插入到ArrayList中的指定位置。 |
addAll(Collection<? extends E> c) | 将指定的集合中的所有元素添加到ArrayList的末尾。 |
addAll(int index, Collection<? extends E> c) | 将指定的集合中的所有元素添加到ArrayList中的指定位置。 |
clear() | 删除ArrayList中的所有元素。 |
clone() | ArrayList浅拷贝。 |
contains(Object o) | 判断ArrayList中是否包含指定元素。 |
ensureCapacity(int minCapacity) | 如有必要,增加ArrayList的容量,以确保它至少能容纳由最小容量参数指定的元素数量。 |
forEach(Consumer<? super E> action) | 将消费操作(Consumer)应用于ArrayList的每一个元素,直到处理完所有数据或操作抛出异常为止。 |
get(int index) | 获取ArrayList中指定位置的元素。 |
indexOf(Object o) | 返回ArrayList中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1。 |
isEmpty() | 判断ArrayList是否为空。 |
iterator() | 返回ArrayList中元素的迭代器。 |
lastIndexOf(Object o) | 返回ArrayList中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
listIterator() | 返回ArrayList中元素的列表迭代器。 |
listIterator(int index) | 从ArrayList中指定的位置开始,返回ArrayList中元素的列表迭代器。 |
remove(int index) | 删除ArrayList中指定位置的元素。 |
remove(Object o) | 从ArrayList中删除第一次出现的指定元素(如果存在)。 |
removeAll(Collection<?> c) | 从ArrayList中删除指定集合中包含的所有元素。 |
removeIf(Predicate<? super E> filter) | 删除ArrayList中满足给定谓词(Predicate)的所有元素。 |
removeRange(int fromIndex, int toIndex) | 从ArrayList中删除索引介于fromIndex(包含)和toIndex(不包括)之间的所有元素。 |
replaceAll(UnaryOperator |
将运算符(UnaryOperator)应用于该元素的结果替换ArrayList中的每个元素。 |
retainAll(Collection<?> c) | 只保留包含在指定集合中的ArrayList中的元素。 |
set(int index, E element) | 用指定的元素替换ArrayList中指定位置的元素。 |
size() | 获取ArrayList大小。 |
sort(Comparator<? super E> c) | 根据指定的比较器对ArrayList进行排序。 |
spliterator() | |
subList(int fromIndex, int toIndex) | 返回指定的fromIndex(包含)和toIndex(不包括)之间的部分list。 |
toArray() | 将ArrayList转换为数组。 |
toArray(T[] a) | 将ArrayList转换为指定类型的数组。 |
trimToSize() | 去除ArrayList中为空的位置,将此ArrayList的容量修改为数组的当前大小。 |
ArrayList源码
ArrayList常量属性
1 | /** |
ArrayList成员变量
1 | /** |
ArrayList内部类
ArrayListSpliterator
TODO
ArrayList构造函数
ArrayList一共有三个构造函数
1 | /** |
ArrayList重要方法解析
arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
1 | public static native void arraycopy(Object src, int srcPos, |
该方法是native修饰的,由C/C++来编写的。
参数解释:
参数名 | 参数解释 |
---|---|
src | 源数组,即被复制的数组 |
srcPos | 源数组被复制的起始位置 |
dest | 目标数组,即源数组的元素会被复制到该数组 |
destPos | 元素放置在目标数组的起始位置 |
length | 复制的长度 |
add(E e)
1 | public boolean add(E e) { |
ensureCapacityInternal(int minCapacity)
1
2
3private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}calculateCapacity(Object[] elementData, int minCapacity)
1
2
3
4
5
6
7
8private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判断是否为空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取最小的容量
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}ensureExplicitCapacity(int minCapacity)
1
2
3
4
5
6
7
8
9private void ensureExplicitCapacity(int minCapacity) {
// 操作次数加1
modCount++;
// overflow-conscious code
// 需要的最小容量比数组的长度大,就调用grow()进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}grow(int minCapacity)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private void grow(int minCapacity) {
// overflow-conscious code
// 获取原数组长度
int oldCapacity = elementData.length;
// 新的大小为原数组的1.5倍
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);
}hugeCapacity(int minCapacity)
1
2
3
4
5
6
7private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}copyOf(T[] original, int newLength)
1
2
3public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}copyOf(U[] original, int newLength, Class<? extends T[]> newType)
1
2
3
4
5
6
7
8
9
10public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 获取目标数组
"unchecked") (
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(E e)
流程:
- 计算需要最小的数组容量
- 操作次数加1
- 判断计算出来的容量是否大于原数组的长度
- 如果计算出来的容量大,就进行扩容
- 扩容是将原数组扩大到1.5倍
- 最后进行添加元素操作,ArrayList大小加1,并返回成功
add(int index, E element)
1 | public void add(int index, E element) { |
rangeCheckForAdd(int index)
1
2
3
4
5private void rangeCheckForAdd(int index) {
// 判断下标是否越界
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
addAll(Collection<? extends E> c)
1 | public boolean addAll(Collection<? extends E> c) { |
addAll(int index, Collection<? extends E> c)
1 | public boolean addAll(int index, Collection<? extends E> c) { |
contains(Object o)
1 | public boolean contains(Object o) { |
indexOf(Object o)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public int indexOf(Object o) {
if (o == null) {
// 判断的对象为null,遍历数组,返回匹配到的下标
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 判断的对象不为null,遍历数组,返回匹配到的下标
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
// 没有匹配到返回-1
return -1;
}
get(int index)
1 | public E get(int index) { |
elementData(int index)
1
2
3
4E elementData(int index) {
// 返回数组中的元素
return (E) elementData[index];
}get(int index)
流程:
- 判断数组下标是否越界
- 根据下标获取数组中的元素
remove(int index)
1 | public E remove(int index) { |
remove(Object o)
1 | public boolean remove(Object o) { |
fastRemove(int index)
1
2
3
4
5
6
7
8
9
10
11
12private void fastRemove(int index) {
// 操作加1
modCount++;
// 计算需要向左移动的元素数量
int numMoved = size - index - 1;
// 如果要移动的数量大于0,进行相关的复制
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组最后一位元素置为null,并且大小减1
elementData[--size] = null; // clear to let GC do its work
}
removeAll(Collection<?> c)
1 | public boolean removeAll(Collection<?> c) { |
batchRemove(Collection<?> c, boolean complement)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29private boolean batchRemove(Collection<?> c, boolean complement) {
// 获取ArrayList数组
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.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
removeIf(Predicate<? super E> filter)
TODO
replaceAll(UnaryOperator operator)
1 | public void replaceAll(UnaryOperator<E> operator) { |
set(int index, E element)
1 | public E set(int index, E element) { |
sort(Comparator<? super E> c)
1 | public void sort(Comparator<? super E> c) { |
trimToSize()
1 | public void trimToSize() { |
为什么ArrayList不能在for-each循环中删除元素
for-each循环时,会调用ArrayList的内部类Itr的hashNext
方法进行判断是否还有下一个元素需要遍历。
hasNext()
1 | public boolean hasNext() { |
可以看出若删除前面的元素,此处就会为true
ps:删除倒数第二个元素,cursor就是最后一个索引值size()-1 ,size也-1了,此处会返回false,不会调用next
方法了(即不存在操作次数的判断)。所以可以删除倒数第二个元素。
如果hashNext
方法返回true,就会继续调用next
方法获取下一个元素。
next()
1 | public E next() { |
我们看一下checkForComodification
方法:
1 | final void checkForComodification() { |
这里的modCount
经过remove之后,已经+1了,所以这里的modCount
和expectModCount
肯定不相等,所以会抛出ConcurrentModificationException
异常。
总结
通过这篇文章,我们对ArrayList进行了更深入的了解,这有利于我们之后使用ArrayList。可以避免遇到一些坑。
ps:有一些TODO的内容,后续有空了再进行添加。
欢迎关注博主其他的文章。