ArrayList源码剖析
1 //默认的初始数组容量(最大长度) 2 private static final int DEFAULT_CAPACITY = 10; 3 //当数组容量为0的时候的默认数组(为什么要声明为static) 4 private static final Object[] EMPTY_ELEMENTDATA = {}; 5 //当数组容量为默认容量时候的默认数组,防止浪费内存空间,所以初始为空,当添加元素时根据默认容量和最少需要的容量哪个大来得到实际容量并生成新的数组。 6 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 7 //ArrayList实际的数据结构形式,一个Object数组 8 transient Object[] elementData; 9 //ArrayList的大小 10 private int size;
ArrayList提供了三种构造器。与LinkedList一样,ArrayList也有无参构造器和参数为集合的构造器,同时ArrayList也提供了参数为整形的构造器,下面是这三个构造器的源码分析。
首先是带有整形参数的构造器,参数表示初始容量。这段代码的逻辑比较简单,判断初始容量参数是否大于等于0,如果小于0则抛出非法参数异常,如果等于0,则当前对象的数组引用静态的空数组,如果大于0,则new一个容量为初始容量参数的Object数组,完成初始化。
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) { 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 }
其次是无参的构造器。我们会惊讶的发现,无参构造器并不是直接new一个容量为默认容量的数组,而是引用了静态的默认容量空数组,之所以这么做我想是因为,由无参构造器产生的ArrayList对象不一定会有元素在其中,为了节省内存空间,先使用静态的默认容量空数组,当添加元素的时候再判断是否进行对elementData进行扩容。
1 public ArrayList() { 2 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 3 }
最后是参数为集合的构造器。泛型的extends保证了集合参数的泛型是当前ArrayList的泛型的类或者其子类。这段代码说明,集合参数通过toArray方法转换为Object数组,之后根据集合参数的大小,如果等于0,则elementData引用静态的空数组,否则再判断elementData是否是Object[]类型(为什么?),如果不是Object[]类型,则调用copyOf方法以Object[]对象的形式复制会elementData
1 public ArrayList(Collection<? extends E> c) { 2 elementData = c.toArray(); 3 if ((size = elementData.length) != 0) { 4 // c.toArray might (incorrectly) not return Object[] (see 6260652) 5 if (elementData.getClass() != Object[].class) 6 elementData = Arrays.copyOf(elementData, size, Object[].class); 7 } else { 8 // replace with empty array. 9 this.elementData = EMPTY_ELEMENTDATA; 10 } 11 }
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 } 6 7 private void grow(int minCapacity) { 8 // overflow-conscious code 9 int oldCapacity = elementData.length; 10 int newCapacity = oldCapacity + (oldCapacity >> 1); 11 if (newCapacity - minCapacity < 0) 12 newCapacity = minCapacity; 13 if (newCapacity - MAX_ARRAY_SIZE > 0) 14 newCapacity = hugeCapacity(minCapacity); 15 // minCapacity is usually close to size, so this is a win: 16 elementData = Arrays.copyOf(elementData, newCapacity); 17 }
add(ine index, E element)方法是将元素插入到指定的下标Index处。首先先检查index是否合法,之后跟add(E e)一样,确保数组容量足够,不同的地方在于,如果是在特定下标index处添加元素,则原本的[index, size-1]下标的元素要全部向后移一个位置,即对应的移动到[index + 1, size' - 1],注意size'=size + 1。
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); 3 4 ensureCapacityInternal(size + 1); // Increments modCount!! 5 System.arraycopy(elementData, index, elementData, index + 1, 6 size - index); 7 elementData[index] = element; 8 size++; 9 }
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 //之所以需要elementData(int index)方法来读取特定下标元素,是因为读操作和写操作不同。ArrayList的底层是Object数组,写操作是绝对安全的(所有的类父类都是Object,所以Object可以引用任何类)。但是读操作不同,读取返回的类型是泛型E,无法确保Object到E的类型转换是安全的,这时候编译器就会发出警告。如果在每一个需要读取元素的地方都是用element[index]的方式获取元素,则编译器会发出大量的警告,不方便我们调试并且看着心烦,如果我们将element[index]封装到一个方法里,在方法里进行强制类型转换,并且用 @SuppressWarnings("unchecked")让编译器闭嘴,则会更美观些哈哈。 6 E oldValue = elementData(index); 7 8 int numMoved = size - index - 1; 9 if (numMoved > 0) 10 System.arraycopy(elementData, index+1, elementData, index, 11 numMoved); 12 elementData[--size] = null; // clear to let GC do its work 13 14 return oldValue; 15 }
remove(Object o)方法与根据下标删除的方法不同,其删除之后不会将被删除的元素返回。这个方法从前向后遍历数组,删除遇到的第一个等于参数o的元素,这里的等于形式的说法是这样的,(o==null)?get(i)==null:o.equals(get(i))。
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 //fastRemove与普通remove不同之处在于其不进行边界检查,因为循环是从0到size-1,肯定不会删到边界之外的元素。 6 fastRemove(index); 7 return true; 8 } 9 } else { 10 for (int index = 0; index < size; index++) 11 if (o.equals(elementData[index])) { 12 fastRemove(index); 13 return true; 14 } 15 } 16 return false; 17 }
1 public E set(int index, E element) { 2 //下标检查, 如果index >= size,则报IndexOutOfBoundsException。为什么不检查index是否为负数呢?因为如果index为负数,则之后的数组访问操作必然会抛出ArrayIndexOutOfBoundsException,没必要重复编码来抛这个异常。 3 //IndexOutOfBoundsException和ArrayIndexOutOfBoundsException的关系和区别是,IndexOutOfBoundsException是发生于例如字符串,数组,以及其他集合在下标超出范围的时候,是ArrayIndexOutOfBoundsException的父类,ArrayIndexOutOfBoundsException是其具体实现,只针对于访问数组的下标为负数或者大于等于数组大小的情况。 4 rangeCheck(index); 5 6 E oldValue = elementData(index); 7 elementData[index] = element; 8 return oldValue; 9 }