ArrayList概述
ArrayList是实现了List接口的动态数组,所谓的动态是其大小可变。允许包括null在内的元素。除了实现List接口外,此类还提供了一些方法来操控内部来储存列表的数组大小。
每个ArrayList实例都有一个容量,该容量是指用来存储元素的数组大小。默认初始容量是10,随着ArrayList元素的增加,它的容量也会一直增加。在每一次增加新的元素的时候,ArrayList都会检查是否需要扩容,扩容操作带来数据向新的数组的COPY,所以我们如果知道具体业务数量(一般不能),在构造ArrayList的时候可以指定一个初始容量,这样会减少扩容操作。
需要注意的是,ArrayList不是同步的,如果多线程访问一个ArrayList实例,而其中至少一个线程从结构上改变了元素列表,那么必须保持外部同步,为了同步,最好是在创建时完成同步操作:
List list = Collection。synchronizedList(new ArrayList(...));
ArrayList源码分析
- 底层使用数组实现
private static final int DEFAULT_CAPACITY = 10;
————————————————————
默认初始容量10
transient Object[] elementData;
+++++++++++++++++
没必要持久化其值
- 构造函数
ArrayList有三个构造函数
ArrayList():默认构造函数,提供初始容量为10的空列表
ArrayList(int initialCapacity):构造一个含有指定初始容量的空列表
public ArrayList(Collection<? extends E> c) :构造一个包含指定collection的元素的列表,
源码:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
——————————————————————————————————
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);
}
}
——————————————————————————
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
以上的代码分别是构造一个空的ArrayList列表,一个有初始容量的空列表,一个指定collection元素的列表,
- 新增(add)
ArrayList提供了add(E e) ,add(index,E e),addAll(Collection(<? extends E> c),addAll(index,Collection(<? extends E> c)(这俩只能添加泛型)set(index,E e)这五种方法向ArrayList添加元素。
add(E e):
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
——————————————————
将指定的元素插入此列表中的尾巴
ensureCapacity方法是扩容操作,elementData[size++] = e;将列表末尾元素指向e
add(index,E e):
public void add(int index, E element) {
//判断索引位置是否正确
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
//扩容检测
ensureCapacity(size+1);
/*
* 对源数组进行复制处理(位移),从index + 1到size-index。
* 主要目的就是空出index位置供数据插入,
* 即向右移动当前位于该位置的元素以及所有后续元素。
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在指定位置赋值
elementData[index] = element;
size++;
}
这个方法中最根本的方法是arraycopy方法 ,该方法的根本目的是将index位置空出来以供新数据的插入,这里需要进行数组的右移,这是很麻烦耗时的。多以如果指定的数据集合要进行大量插入的话,建议使用linkedList。
addAll(Collection(<? extends E> c):按照指定collection的迭代器返回的元素顺序,将添加的元素放在列表的末尾
public boolean addAll(Collection<? extends E> c) {
// 将集合C转换成数组
Object[] a = c.toArray();
int numNew = a.length;
// 扩容处理,大小为size + numNew
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。
addAll(index,Collection(<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
//判断位置是否正确
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
//转换成数组
Object[] a = c.toArray();
int numNew = a.length;
//ArrayList容器扩容处理
ensureCapacity(size + numNew); // Increments modCount
//ArrayList容器数组向右移动的位置
int numMoved = size - index;
//如果移动位置大于0,则将ArrayList容器的数据向右移动numMoved个位置,确保增加的数据能够增加
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//添加数组
System.arraycopy(a, 0, elementData, index, numNew);
//容器容量变大
size += numNew;
return numNew != 0;
}
set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) {
//检测插入的位置是否越界
RangeCheck(index);
E oldValue = (E) elementData[index];
//替代
elementData[index] = element;
return oldValue;
}
- 删除
ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四个方法进行元素的删除。
remove(int index):移除此列表中指定位置上的元素。
remove(Object o):移除此列表中首次出现的指定元素(如果存在)。
removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和toIndex(不包括)之间的所有元素。
- 查找
ArrayList提供了get(int index)用读取ArrayList中的元素。由于ArrayList是动态数组,所以我们完全可以根据下标来获取ArrayList中的元素,而且速度还比较快,故ArrayList长于随机访问。
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
- 扩容
我们可以看到,在新增方法的源代码都含有一个函数,ensureCapacity()方法,该方法就是扩容方法,如果新增元素后容量大过初始容量。就会进行扩容操作。所以当我们知道业务数据量或者需要插入大量元素的时候,我们使用ensureCapacity()方法来手动添加初始容量,以减少再扩容的操作
为什么每次扩容处理会是1.5倍,而不是2.5、3、4倍呢?通过google查找,发现1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。