ArrayList源码浅析
一、ArrayList简介
ArrayList是Java集合类中使用最频繁的几个集合类之一,它的底层是可以动态增长和缩减的数组。先来看一下ArrayList的继承关系图:
可以看到ArrayList的顶层集合类为Collection。ArrayList实现了List接口,继承了AbstracList类,提供了数组集合相关的增、删、改、查和遍历等功能;实现了RandomAccess接口,提供了随机访问的能力。
因为ArrayList的底层实现是数组,所以其在在内存中是连续的一块内存,因此其根据数组下标随机访问的效率很高,插入和删除的效率较低。当集合中的元素数量超出数组容量的时候,便会进行扩容操作,扩容操作是ArrayList集合一个比较耗费性能的操作,因此如果我们提前可以预知数据的规模,应该在初始化的时候就直接确定ArrayList的大小,避免之后的扩容操作,提高效率。
二、源码浅析
1、属性和构造方法
/** * 默认容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 初始容量为0的构造方法,设置数组容量为EMPTY_ELEMENTDATA */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 无参构造设置的数组大小 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 存储ArrayList元素的数组。 */ transient Object[] elementData; /** * 数组大小 */ private int size; /** * 创建ArrayList实例时,设置初始容量 */ 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() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 以一个Collection实例初始化ArrayList */ public ArrayList(Collection<? extends E> c) { //返回一个包含此集合中所有元素的数组。 elementData = c.toArray(); //collection长度不为 0 时,给 size 赋值 if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) //当 c.toArray 出错的情况下没有返回 Object[] ,将 elementData 的类型指定为 Object[] if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
构造方法执行完之后,ArrayList实例中的elementData和size属性就已经确定了。
2、新增元素及扩容
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } /** * 计算ArrayList实例的容量 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果当前ArrayList实例大小为"空",那么但会缺省容量(10)或者minCapacity(当前size+1)中大的那个数 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //否则返回minCapacity(当前size+1) return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; //如果calculateCapacity()方法返回的数组容量大于当前数组的容量,则扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * 执行扩容 */ private void grow(int minCapacity) { //oldCapacity指向当前数组容量大小 int oldCapacity = elementData.length; //新容量扩增为以前的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新容量比传入最小容量还小,那么将传入容量为新容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新容量比最大数组大小还大,比较参数minCapacity,最终确定数据大小为Integer.MAX_VALUE或MAX_ARRAY_SIZE 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; }
/** * * 在此列表中的指定位置插入指定的元素。 插入,原来的向后重新排列 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); //将集合转化为对象数组 int numNew = a.length; //记录新数组长度 ensureCapacityInternal(size + numNew); //对当前ArrayList进行扩容 System.arraycopy(a, 0, elementData, size, numNew); //将新数组的值copy到原数组之后,append size += numNew; return numNew != 0; } public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; //需要移动的元素个数 if (numMoved > 0) //将index及之后的元素向后移动a.length的长度 System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //将需要插入的集合c数组插入 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
ArrayList的扩容流程图(请别介意图很low):
3、删除元素及缩容
/** * 移除指定索引处的元素 */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); //取出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; } /** * 删除该元素在数组中第一次出现的位置上的数据。 如果有该元素返回true,如果false。 */ 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; } /** * 跳过边界检查、不返回删除的数据 */ private void fastRemove(int index) { modCount++; 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 } protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; //将原数组toIndex之后剩余的元素拷贝到从fromIndex处到numMoved处 System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; //多余元素置为null } size = newSize; } /** * 将此 ArrayList 实例的容量(底层数组length)调整为列表的当前大小(size)。 */ public void trimToSize() { modCount++; //此列表已被结构修改的次数 if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
4、查询及修改
public E get(int index) { rangeCheck(index); return elementData(index); } public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } E elementData(int index) { return (E) elementData[index]; }
5、其他常用api
/** * 返回当前ArrayList实例数组是否为空数组 */ public boolean isEmpty() { return size == 0; } /** * 返回list实例是否包含元素o */ public boolean contains(Object o) { //可以判断obj是null的情况 return indexOf(o) >= 0; } /** * 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。 */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-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; } public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; //将新实例的modCount(此列表已被结构修改的次数)置为0 return v; //返回此ArrayList实例的浅拷贝 } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
三、ArrayList总结
1、ArrayList底层是数组,随机访问效率高、插入删除效率低,迭代效率一般。在尾部新增数据时因为不涉及数据的移动,效率并不低。
2、新增数据可能会导致数组扩容,降低性能。因此尽可能减少扩容次数,在初始化的时候就为数组指定一个合适的初始容量。
3、ArrayList中的所有方法都不是同步方法,线程非安全。在多线程编程中,需要注意这点。
4、ArrayList常用来与LinkedList和Vector相比较,它们之间的一些异同关系也需要掌握,这里就不赘述了。