前言:之前学习hashmap,重点学习了hashmap的数据结构以及如何扩容,相关联的其实有ArrayList、LinkedList和HashTable这些,本文简单地学习一下ArrayList。
PS: 源码分析针对于Android O 的jdk,即jdk1.8
demo地址:jiatai的ArrayList demo
1. ArrayList简单介绍
就我印象,ArrayList在Java中扮演的角色对应于数据结果中的数组,或者说ArrayList是基于数组写出来的一个类,用于存储数据。
说一个小问题,如果我想在ArrayList里存12个数,是用无参的ArrayList好,还是有参的好,好在哪里?(有参的好,好在不用扩容)
2. ArrayList简单使用
简单的往Arraylist里面塞两个数字1和2:package com.example.demo_26_arraylist; import java.util.ArrayList; public class MyClass { public static void main(String[] args){ ArrayList arrayList = new ArrayList<Integer>(); System.out.println("the size is " + arrayList.size()); arrayList.add(1); System.out.println("the size is " + arrayList.size()); arrayList.add(2); System.out.println("the size is " + arrayList.size()); System.out.println(arrayList); } }
对应log:
the size is 0 the size is 1 the size is 2 [1, 2]
3. 简单使用所对应的内在
上面的使用主要涉及了四个方法
- 构造方法
- add()
- size()
- toString()
下面来简单地看下内在=-=
3.1 构造方法
构造方法有3个,这里先看下平常用的最多的无参构造方法。/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
无参构造方法是最简单的,也是我们比较常用的,内部其实就初始化了一下elementData这个成员变量,来看下elementData 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是啥?
/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ // Android-note: Also accessed from java.util.Collections transient Object[] elementData; // non-private to simplify nested class access
DEFAULTCAPACITY_EMPTY_ELEMENTDATA其实就是个空的数组。而elementData这里注释讲的很清楚,是用来存放数组成员的数组缓存,Arraylist的长度就是这个elementData的长度。并且任何空的数组,也就是用无参构造器初始的数组,在add第一个成员的时候会被扩展成DEFAULT_CAPACITY。
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
也就是说空的数组第一次被add元素的时候会被扩展为容量为10的数组。
这里接着就看下add的源码。
3.2 add(E e)
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
可以看到add分两个步骤
- 确保容量足够存储新来的成员
- 添加成员,并使size++
这里主要看下ensureCapacityInternal这个方法的后续:
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
这里可以看到
1.将minCapacity初始化为DEFAULT_CAPACITY,即10,这和之前看无参构造器注释一样的。
2.依据minCapacity的值调用grow方法进行扩容
/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; 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); }
这边扩容不是说直接用 minCapacity 为ArrayList的大小就开始扩容了。
举个例子:现在数组长度为10,则新的扩容建议大小为10+10/2,即15,然后以15和minCapacity(11)继续比较,15比较大,所以当数组长度为10的数组新添一个元素后,长度会变为15。(第4节中有例子来验证)
对应于初始化就是minCapacity是10,而oldCapacity是0,没有可比较性,直接用minCapacity作为扩容后数组大小。
确定好扩容后数组大小后,用Arrays的copyOf方法将原数组元素复制到新的数组中。
/** * Copies the specified array, truncating or padding with nulls (if necessary) * so the copy has the specified length. For all indices that are * valid in both the original array and the copy, the two arrays will * contain identical values. For any indices that are valid in the * copy but not the original, the copy will contain <tt>null</tt>. * Such indices will exist if and only if the specified length * is greater than that of the original array. * The resulting array is of exactly the same class as the original array. * * @param <T> the class of the objects in the array * @param original the array to be copied * @param newLength the length of the copy to be returned * @return a copy of the original array, truncated or padded with nulls * to obtain the specified length * @throws NegativeArraySizeException if <tt>newLength</tt> is negative * @throws NullPointerException if <tt>original</tt> is null * @since 1.6 */ @SuppressWarnings("unchecked") public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); }
3.3 size
注意区分size不是Arraylist的实际长度,而是其中包含的元素的数目,对应与上面的“当数组大小为10的时候,再添加一个元素会造成数组进行扩容为15”的例子,size是11,而不是15,elementData的数组长度才是15。/** * Returns the number of elements in this list. * * @return the number of elements in this list */ public int size() { return size; }
另外试了下arrayList是支持添加null元素的,并且size可以正常进行+1
3.4 toString
toString方法是继承的父类AbstractCollection的toString方法,将其所包含的元素都打印出来。/** * Returns a string representation of this collection. The string * representation consists of a list of the collection's elements in the * order they are returned by its iterator, enclosed in square brackets * (<tt>"[]"</tt>). Adjacent elements are separated by the characters * <tt>", "</tt> (comma and space). Elements are converted to strings as * by {@link String#valueOf(Object)}. * * @return a string representation of this collection */ public String toString() { Iterator<E> it = iterator(); if (! it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder(); sb.append('['); for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (! it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } }
3.5 指定位置插入add(int index, E element)
数组在指定位置插入元素效率比较低的,从下面源码可以看出,是将指定位置及后面的源码都往后挪一下,给指定元素挪一个位置出来让该元素落座。remove方法就是相反的操作=-=/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
4. 扩容
扩容其实在3.2节中有提及,如果不考虑边界情况,即初始化为10和数目大于整数最大值的时候,扩容其实就是将数组大小变为n+n/2,然后调用Arrays.copyOf方法将之前的数组成员复制到新数组中去并返回新数组。
写了个简单的例子来验证上面说的ArrayList的扩容原理和之前举的例子:
当数组大小为10的时候,再添加一个元素会造成数组进行扩容为15,其中4个空档会用null占据。
5. 总结
ArrayList本质可以看成是一个固定大小的数组,然后一直往里面塞成员,不够塞了就扩容一下,继续赛。这里想起来之前阿里编码规范里有说,如果确定了ArrayList大小,则指定大小,不然比如10到11会造成一次数组扩容,影响效率。