java.util.ArrayList
以下为主要介绍要点,从 Java 8 出发:
- 一、ArrayList的特点概述
- 二、ArrayList的内部实现:从内部变量和构造函数说起
- 三、ArrayList添加元素和扩容
- 四、ArrayList删除元素
- 五、ArrayList查找元素
- 六、ArrayList修改元素
- 七、ArrayList的内部元素elementData为何用transient修饰
- 八、ArrayList和Vector的比较
- 九、Java8中ArrayList的改动
一、ArrayList的特点概述
这里先入为主,从ArrayList本身特点出发开始介绍,结论如下:
关注点 | ArrayList相关结论 |
是否允许空的元素 | 是 |
是否允许重复的元素 | 是 |
元素有序:读取数据和存放数据的顺序一致 | 是 |
是否线程安全 | 否 |
随机访问的效率 | 随机访问指定索引(数组的索引)的元素快 |
顺序添加元素的效率 | 在不涉及扩容时,顺序添加元素速度快; 当需要扩容时,涉及到元素的复制,相对较慢 |
删除和插入元素的效率 | 因涉及到复制和移动后续的元素,相对较慢 |
二、ArrayList的内部实现:从内部变量和构造函数说起
ArrayList是一个内部以数组方式实现列表、可以自动扩容的集合。其内部实现有5个重要的变量:
- DEFAULT_CAPACITY:静态变量,是默认的初始化元素个数(容量)。
- EMPTY_ELEMENTDATA:静态变量,所有对象实例共享,是默认的空实现。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA:静态变量,所有对象实例共享,是默认的空实现,所有调用空构造器public ArrayList()构造的ArrayLis默认指向该变量。
- elementData:是常规的数组,存储列表的元素。这也是读取数据和存放数据的顺序一致、随机访问指定元素、顺序添加元素快(在末尾添加,且不涉及扩容的情况下)的原因。
- size:elementData数组中实际占用的有效数组元素个数。
源码如下:
1 /** 2 * Default initial capacity. 3 */ 4 private static final int DEFAULT_CAPACITY = 10; 5 6 /** 7 * Shared empty array instance used for empty instances. 8 */ 9 private static final Object[] EMPTY_ELEMENTDATA = {}; 10 11 /** 12 * Shared empty array instance used for default sized empty instances. We 13 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 14 * first element is added. 15 */ 16 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 17 18 /** 19 * The array buffer into which the elements of the ArrayList are stored. 20 * The capacity of the ArrayList is the length of this array buffer. Any 21 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 22 * will be expanded to DEFAULT_CAPACITY when the first element is added. 23 */ 24 transient Object[] elementData; // non-private to simplify nested class access 25 26 /** 27 * The size of the ArrayList (the number of elements it contains). 28 * 29 * @serial 30 */ 31 private int size; 32 33 /** 34 * The maximum size of array to allocate. 35 * Some VMs reserve some header words in an array. 36 * Attempts to allocate larger arrays may result in 37 * OutOfMemoryError: Requested array size exceeds VM limit 38 */ 39 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
三、ArrayList添加元素和扩容
源码如下,
1 /** 2 * Appends the specified element to the end of this list. 3 * 4 * @param e element to be appended to this list 5 * @return <tt>true</tt> (as specified by {@link Collection#add}) 6 */ 7 public boolean add(E e) { 8 ensureCapacityInternal(size + 1); // Increments modCount!! 9 elementData[size++] = e; 10 return true; 11 }
add方法调用的了ensureCapacityInternal(size + 1),首先是判断元素的容量(capacity)大小,如果小于默认的DEFAULT_CAPACITY = 10,则使用默认容量,可以看出,只要添加了数组元素,数组的最小容量就是10。而ensureCapacityInternal(size + 1)调用了ensureExplicitCapacity(minCapacity),这个方法记录了总共需要扩容的次数modCount,同时进行了实际的数组扩容。当然只有实际数组长度size超过时才会进行扩容。
1 private void ensureCapacityInternal(int minCapacity) { 2 if (elementData == EMPTY_ELEMENTDATA) { 3 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 4 } 5 6 ensureExplicitCapacity(minCapacity); 7 } 8 9 private void ensureExplicitCapacity(int minCapacity) { 10 modCount++; 11 12 // overflow-conscious code 13 if (minCapacity - elementData.length > 0) 14 grow(minCapacity); 15 }
扩容的方法如下。对于没有溢出的,实际的扩容是增加了一半容量,这里使用了位运算,右移一位类似与/2操作,取了一半,但是位操作快。
这里需要说明的是,默认空构造器时建立的ArrayList也是在这里首次进行扩容的,使用默认容量10。
1 /** 2 * Increases the capacity to ensure that it can hold at least the 3 * number of elements specified by the minimum capacity argument. 4 * 5 * @param minCapacity the desired minimum capacity 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 } 18 19 private static int hugeCapacity(int minCapacity) { 20 if (minCapacity < 0) // overflow 21 throw new OutOfMemoryError(); 22 return (minCapacity > MAX_ARRAY_SIZE) ? 23 Integer.MAX_VALUE : 24 MAX_ARRAY_SIZE; 25 }
elementData[size++] = e;就是直接的元素赋值,然后增加size。另外还有指定索引添加元素的,代码如下。从这两处源码可以看到,ArrayList并没有判断元素是什么,而是直接存储了,因此说:ArrayList允许空的或者重复的元素。
- 判断越界
- 扩容
- 从指定索引处开始的元素都往后移动一位
- 插入指定索引的指定元素,size加1,完成。
从这里可以看出,因涉及到复制和移动其他的元素,插入元素比较慢。删除也是类似的。
1 /** 2 * Inserts the specified element at the specified position in this 3 * list. Shifts the element currently at that position (if any) and 4 * any subsequent elements to the right (adds one to their indices). 5 * 6 * @param index index at which the specified element is to be inserted 7 * @param element element to be inserted 8 * @throws IndexOutOfBoundsException {@inheritDoc} 9 */ 10 public void add(int index, E element) { 11 rangeCheckForAdd(index); 12 13 ensureCapacityInternal(size + 1); // Increments modCount!! 14 System.arraycopy(elementData, index, elementData, index + 1, 15 size - index); 16 elementData[index] = element; 17 size++; 18 }
四、ArrayList删除元素
删除元素有2种:
- 指定索引删除元素
- 指定元素删除:这里找到第一个equals或者null(如元素为null)即可
1. 指定索引删除元素
- 越界判断
- 修改总共需要扩容的次数modCount
- 记录指定索引的旧元素
- 非最后一个元素,即index != size - index - 1,把元素前移一个单位。
- 清空最后一个索引元素
- 返回旧元素
1 /** 2 * Removes the element at the specified position in this list. 3 * Shifts any subsequent elements to the left (subtracts one from their 4 * indices). 5 * 6 * @param index the index of the element to be removed 7 * @return the element that was removed from the list 8 * @throws IndexOutOfBoundsException {@inheritDoc} 9 */ 10 public E remove(int index) { 11 rangeCheck(index); 12 13 modCount++; 14 E oldValue = elementData(index); 15 16 int numMoved = size - index - 1; 17 if (numMoved > 0) 18 System.arraycopy(elementData, index+1, elementData, index, 19 numMoved); 20 elementData[--size] = null; // clear to let GC do its work 21 22 return oldValue; 23 }
2. 指定元素删除
删除的步骤类似,只是这里是遍历找到第一个指定的元素而已
1 /** 2 * Removes the first occurrence of the specified element from this list, 3 * if it is present. If the list does not contain the element, it is 4 * unchanged. More formally, removes the element with the lowest index 5 * <tt>i</tt> such that 6 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> 7 * (if such an element exists). Returns <tt>true</tt> if this list 8 * contained the specified element (or equivalently, if this list 9 * changed as a result of the call). 10 * 11 * @param o element to be removed from this list, if present 12 * @return <tt>true</tt> if this list contained the specified element 13 */ 14 public boolean remove(Object o) { 15 if (o == null) { 16 for (int index = 0; index < size; index++) 17 if (elementData[index] == null) { 18 fastRemove(index); 19 return true; 20 } 21 } else { 22 for (int index = 0; index < size; index++) 23 if (o.equals(elementData[index])) { 24 fastRemove(index); 25 return true; 26 } 27 } 28 return false; 29 } 30 31 /* 32 * Private remove method that skips bounds checking and does not 33 * return the value removed. 34 */ 35 private void fastRemove(int index) { 36 modCount++; 37 int numMoved = size - index - 1; 38 if (numMoved > 0) 39 System.arraycopy(elementData, index+1, elementData, index, 40 numMoved); 41 elementData[--size] = null; // clear to let GC do its work 42 }
五、ArrayList查找元素
这里只做了越界检查,如果没有越界,直接返回指定索引的元素即可,因此说速度比较快。
1 /** 2 * Returns the element at the specified position in this list. 3 * 4 * @param index index of the element to return 5 * @return the element at the specified position in this list 6 * @throws IndexOutOfBoundsException {@inheritDoc} 7 */ 8 public E get(int index) { 9 rangeCheck(index); 10 11 return elementData(index); 12 }
六、ArrayList修改元素
指定索引修改元素,按照索引,速度也是比较快。
1 /** 2 * Replaces the element at the specified position in this list with 3 * the specified element. 4 * 5 * @param index index of the element to replace 6 * @param element element to be stored at the specified position 7 * @return the element previously at the specified position 8 * @throws IndexOutOfBoundsException {@inheritDoc} 9 */ 10 public E set(int index, E element) { 11 rangeCheck(index); 12 13 E oldValue = elementData(index); 14 elementData[index] = element; 15 return oldValue; 16 }
七、ArrayList的内部元素elementData为何用transient修饰
ArrayList本身实现了Cloneable和Serializable,但是关键的成员变量却是用了transient进行了修饰,不希望被序列化。
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从序列化的实现来看,只对elementData中有元素的部分进行了序列化,并没有全部元素,这是合理的,一般elementData的容量比实际的size大,没有必要所有元素都行序列化。这也提高了时间效率,同时节省了空间。
1 /** 2 * Save the state of the <tt>ArrayList</tt> instance to a stream (that 3 * is, serialize it). 4 * 5 * @serialData The length of the array backing the <tt>ArrayList</tt> 6 * instance is emitted (int), followed by all of its elements 7 * (each an <tt>Object</tt>) in the proper order. 8 */ 9 private void writeObject(java.io.ObjectOutputStream s) 10 throws java.io.IOException{ 11 // Write out element count, and any hidden stuff 12 int expectedModCount = modCount; 13 s.defaultWriteObject(); 14 15 // Write out size as capacity for behavioural compatibility with clone() 16 s.writeInt(size); 17 18 // Write out all elements in the proper order. 19 for (int i=0; i<size; i++) { 20 s.writeObject(elementData[i]); 21 } 22 23 if (modCount != expectedModCount) { 24 throw new ConcurrentModificationException(); 25 } 26 }
八、ArrayList和Vector的比较
1、Vector是线程安全的:Vector大部分方法都和ArrayList差不多,但是其实现都添加了synchronized来保证操作的安全性。
2、Vector可以指定扩容的增长因子capacityIncrement,每次需要扩容时会根据扩容因子进行判断,直接扩展指定的因子,或者是倍增,如下:
1 private void grow(int minCapacity) { 2 // overflow-conscious code 3 int oldCapacity = elementData.length; 4 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? 5 capacityIncrement : oldCapacity); 6 //判断是否溢出 7 if (newCapacity - minCapacity < 0) 8 newCapacity = minCapacity; 9 if (newCapacity - MAX_ARRAY_SIZE > 0) 10 newCapacity = hugeCapacity(minCapacity); 11 elementData = Arrays.copyOf(elementData, newCapacity); 12 }
Java8中的ArrayList对于以上方法基本没有改动,只是增加了DEFAULTCAPACITY_EMPTY_ELEMENTDATA,作为默认空构造器时的空实现,而原来的EMPTY_ELEMENTDATA则改为在指定容量为0时的空实现。
1 /** 2 * Shared empty array instance used for empty instances. 3 */ 4 private static final Object[] EMPTY_ELEMENTDATA = {}; 5 6 /** 7 * Shared empty array instance used for default sized empty instances. We 8 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 9 * first element is added. 10 */ 11 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
其他: 遍历和ConcurrentModificationException