Java 集合--ArrayList
ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null
元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。
每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。
初始容量为10,按照1.5倍扩容。
ArrayList 有个 trimToSize() 方法用来缩小 elementData 数组的大小,这样可以节约内存。考虑这样一种情形,当某个应用需要,一个 ArrayList 扩容到比如 size=10000,之后经过一系列 remove 操作 size=15,在后面的很长一段时间内这个 ArrayList 的 size 一直保持在 < 100 以内,那么就造成了很大的空间浪费,这时候建议显式调用一下 trimToSize() 这个方法,以优化一下内存空间。或者在一个 ArrayList 中的容量已经固定,但是由于之前每次扩容都扩充 50%,所以有一定的空间浪费,可以调用 trimToSize() 消除这些空间上的浪费。
ArrayList的继承关系
java.lang.Object ↳ java.util.AbstractCollection<E> ↳ java.util.AbstractList<E> ↳ java.util.ArrayList<E> public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
ArrayList构造函数
// 默认构造函数 ArrayList() // capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。 ArrayList(int capacity) // 创建一个包含collection的ArrayList ArrayList(Collection<? extends E> collection)
ArrayList的API
// Collection中定义的API boolean add(E object) boolean addAll(Collection<? extends E> collection) void clear() boolean contains(Object object) boolean containsAll(Collection<?> collection) boolean equals(Object object) int hashCode() boolean isEmpty() Iterator<E> iterator() boolean remove(Object object) boolean removeAll(Collection<?> collection) boolean retainAll(Collection<?> collection) int size() <T> T[] toArray(T[] array) Object[] toArray() // AbstractCollection中定义的API void add(int location, E object) boolean addAll(int location, Collection<? extends E> collection) E get(int location) int indexOf(Object object) int lastIndexOf(Object object) ListIterator<E> listIterator(int location) ListIterator<E> listIterator() E remove(int location) E set(int location, E object) List<E> subList(int start, int end) // ArrayList新增的API Object clone() void ensureCapacity(int minimumCapacity) void trimToSize() void removeRange(int fromIndex, int toIndex)
ArrayList源码分析
基于jdk1.7
ArrayList类的属性:
private static final int DEFAULT_CAPACITY = 10; // 集合的默认容量 private static final Object[] EMPTY_ELEMENTDATA = {}; // 一个空集合数组,容量为0 private transient Object[] elementData; // 存储集合数据的数组,默认值为null private int size; // ArrayList集合中数组的当前有效长度,比如数组的容量是5,size是1 表示容量为5的数组目前已经有1条记录了,其余4条记录还是为空
三个构造函数:
public ArrayList(int initialCapacity) { // 带有集合容量参数的构造函数 // 调用父类AbstractList的方法构造函数 super(); if (initialCapacity < 0) // 如果集合的容量小于0,这明显是个错误数值,直接抛出异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; // 初始化elementData属性,确定容量 } public ArrayList() { // 没有参数的构造函数 super(); // 调用父类AbstractList的方法构造函数 this.elementData = EMPTY_ELEMENTDATA; // 让elementData和ArrayList的EMPTY_ELEMENTDATA这个空数组使用同一个引用 } public ArrayList(Collection<? extends E> c) { // 参数是一个集合的构造函数 elementData = c.toArray(); // elementData直接使用参数集合内部的数组 size = elementData.length; // 初始化数组当前有效长度 // c.toArray方法可能不会返回一个Object[]结果,需要做一层判断。这个一个Java的bug,可以在http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652查看 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
add(E e) 方法:
public boolean add(E e) { ensureCapacityInternal(size + 1); // 调用ensureCapacityInternal,参数是集合当前的长度。确保集合容量够大,不够的话需要扩容 elementData[size++] = e; // 数组容量够的话,直接添加元素到数组最后一个位置即可,同时修改集合当前有效长度 return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { // 如果数组是个空数组,说明调用的是无参的构造函数 // 如果调用的是无参构造函数,说明数组容量为0,那就需要使用默认容量 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 如果集合需要的最小长度比数组容量要大,那么就需要扩容,已经放不下了 if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); }
add(int index, E element) 方法:
这个方法的作用是 在指定位置插入数据,该方法的缺点就是如果集合数据量很大,移动元素位置将会花费不少时间。
public void add(int index, E element) { rangeCheckForAdd(index); // 检查索引位置的正确的,不能小于0也不能大于数组有效长度 ensureCapacityInternal(size + 1); // 扩容检测 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 移动数组位置,数据量很大的话,性能变差 elementData[index] = element; // 指定的位置插入数据 size++; // 数组有效长度+1 }
上图就表示要在容量为5的数组中的第4个位置插入6这个元素,会进行3个步骤:
- 容量为5,再次加入元素,需要扩容,扩容出2个白色的空间
- 扩容之后,5和4这2个元素都移到后面那个位置上
- 移动完毕之后空出了第4个位置,插入元素6
remove(int index):
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; // 清楚对应位置上的对象,让gc回收 return oldValue; }
比如要移除5个元素中的第3个元素,首先要把4和5这2个位置的元素分别set到3和4这2个位置上,set完之后最后一个位置也就是第5个位置set为null。
remove(Object o)
// 跟remove索引元素一样,这个方法是根据equals比较 public boolean remove(Object o) { if (o == null) { // ArrayList允许元素为null,所以对null值的删除在这个分支里进行 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { // 效率比较低,需要从第1个元素开始遍历直到找到equals相等的元素后才进行删除,删除同样需要移动元素 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
clear
清除list中的所有数据
public void clear() { modCount++; // 遍历集合数据,全部set为null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; // 数组有效长度变成0 }
set(int index, E element)
用element值替换下标值为index的值
public E set(int index, E element) { rangeCheck(index); // 检查索引值是否合法 E oldValue = elementData(index); elementData[index] = element; // 直接替换 return oldValue; }
get(int index)
得到下标值为index的元素
public E get(int index) { rangeCheck(index); // 检查索引值是否合法 return elementData(index); // 直接返回下标值 }
addAll
在列表的结尾添加一个Collection集合
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // 扩容检测 System.arraycopy(a, 0, elementData, size, numNew); // 直接在数组后面添加新的数组中的所有元素 size += numNew; // 更新有效长度 return numNew != 0; }
toArray
根据elementData数组拷贝一份新的数组
public Object[] toArray() { return Arrays.copyOf(elementData, size); }
ArrayList的注意点
- 当数据量很大的时候,ArrayList内部操作元素的时候会移动位置,很耗性能
- ArrayList虽然可以自动扩展长度,但是数据量一大,扩展的也多,会造成很多空间的浪费
- ArrayList有一个内部私有类,SubList。ArrayList提供一个subList方法用于构造这个SubList。这里需要注意的是SubList和ArrayList使用的数据引用是同一个对象,在SubList中操作数据和在ArrayList中操作数据都会影响双方。
- ArrayList允许加入null元素