Java集合之List——ArrayList详解
简介
ArrayList
是List接口的大小可变数组的实现类(容量可自动增长);ArrayList
是非同步的;ArrayList
的iterator
和listIterator
方法返回的迭代器是fail-fast的。
源码解读
继承关系
1 | public class ArrayList<E> extends AbstractList<E> |
结构图:
先简单介绍一下RandomAccess
、Cloneable
、Serializable
三个接口:
RandomAccess
:与使用效率相关,表明ArrayList
支持快速(通常是固定时间)随机访问;Cloneable
:实现了该接口,就可以使用Object.clone
方法,就是ArrayList
的拷贝,返回类型为Object
,注意写法的问题,测试代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Demo01 {
public static void main(String[] args) {
Integer[] arr = new Integer[]{1, 44, 3, 4, 5, 6, 7, 8, 9};
//正确写法
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, arr);
Object list2 = list.clone();
/*错误写法
List<Integer> list = new ArrayList<>();
Collections.addAll(list, arr);
Object list2 = list.clone();//报错,List中找不到clone方法
*/
}
}Serializable
:表明该类具有序列化功能,该类可以被序列化。
再介绍一下AbstractList
:
- 通过看源码发现这是一个抽象类,发现他又继承了一个抽象类
AbstractCollection
和实现了接口List
(由上方结构图可以清晰看出)。 - 通过查阅资料,这么安排继承关系是因为,可以从以该类为父类的子类中抽取一些实现相同的方法,将其实现为一个抽象类,以达到代码复用和代码简洁的目的。
- 还有一个疑问是为什么抽象类
AbstractCollection
已经实现了接口List
,ArrayList
还要实现List
接口呢?具体原因可见。 - 这里简单描述一下:开发
collection
的作者Josh说这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。成员变量
仅介绍部分。
集合中所有元素均存储在数组中,类中的定义如下:首先数组的权限设定为了1
transient Object[] elementData; // non-private to simplify nested class access
Default
,是为了方便内部类地使用;
其次用了关键字transient
进行修饰,它表示该变量将不被序列化处理。具体可参考
1 | private int size;//代表集合当前的大小 |
构造方法
public ArrayList();
,构造一个初始容量为10的空列表public ArrayList(int initialCapacity);
,构造一个容量为initialCapacity
(给定参数)的空列表public ArrayList(Collection<? extends E> c);
,构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回
1 | /** |
扩容机制
以下以add
方法为例1
2
3
4
5public boolean add(E e) {
ensureCapacityInternal(size + 1); // 保证容器大小够用
elementData[size++] = e;
return true;
}
- 通过
ensureCapacityInternal
方法来保证内部容量足够容纳新添加的元素(可能是多个元素,如addAll
方法)1
2
3
4private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); //calculateCapacity返回添加元素后容器的最小容量
//ensureExplicitCapacity先判断容器是否需要扩容,若需要扩容则调用扩容函数grow
} - 然后通过
calculateCapacity
方法来确定添加元素后容器的最小容量1
2
3
4
5
6private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} - 再通过
ensureExplicitCapacity
方法判断容器是否需要扩容1
2
3
4
5
6private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} - 若需要扩容紧接着调用扩容方法
grow
总结:ArrayList在每次插入新元素时,总是先保证容量够用,然后再插入新元素,而每次扩容为原容量的1.5倍。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private void grow(int minCapacity) {
// overflow-conscious code
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);//判断是否超出数组最大容量,然后相应做处理
// 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;
}
关于为什么是1.5倍:如果扩容太多会导致空间的浪费,而扩容太少会导致频繁进行扩容,而1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。常用方法
添加元素
1
2
3
4boolean add(E e) //将指定的元素追加到此列表的末尾。
void add(int index, E element) //在此列表中的指定位置插入指定的元素。
boolean addAll(Collection<? extends E> c) //按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
boolean addAll(int index, Collection<? extends E> c) //将指定集合中的所有元素插入到此列表中,从指定的位置开始。删除元素
1
2
3
4
5
6E remove(int index) //删除该列表中指定位置的元素。
boolean remove(Object o) //从列表中删除指定元素的第一个出现(如果存在)。
boolean removeAll(Collection<?> c) //从此列表中删除指定集合中包含的所有元素,即集合取差集 。
boolean removeIf(Predicate<? super E> filter) //删除满足给定谓词的此集合的所有元素,利用正则表达式筛选出取出符合条件的。
protected void removeRange(int fromIndex, int toIndex) //从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
boolean retainAll(Collection<?> c) //仅保留此列表中包含在指定集合中的元素,集合取交集。修改元素
1
2E set(int index, E element) //用指定的元素替换此列表中指定位置的元素。
void replaceAll(UnaryOperator<E> operator) //将该列表的每个元素替换为将该运算符应用于该元素的结果。获取元素
1
2
3E get(int index) //返回此列表中指定位置的元素。
int indexOf(Object o) //返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
int lastIndexOf(Object o) //返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。其它方法
1
2
3
4
5void clear() //从列表中删除所有元素。
boolean contains(Object o) //如果此列表包含指定的元素,则返回 true 。
boolean isEmpty() //如果此列表不包含元素,则返回 true 。
int size() //返回此列表中的元素数。
List<E> subList(int fromIndex, int toIndex) //返回此列表中指定的 fromIndex (包括)和 toIndex之间的独占视图。效率分析
arrayList
由于本质是数组,所以它在数据的查询方面会很快;而在插入删除元素过程时,如果操作在数组末尾,时间复杂度为O(1),而在操作在指定位置,时间复杂度为O(n),性能会下降很多,因为需要挪动元素。
综上ArrayList
,在查询多,增删(在List
前段位置)操作少时比较合适。