ArrayList 这个类应该是平时代码中最常用的一种列表实现类,在此通过查看源码来进一步理解常用方法,同时了解一下不常用的方法,应该会有惊喜。这次换个方式来看,以方法熟悉度去看对应源码,有必要的在贴源码。
1. ArrayList 头部注释
此类从 java 1.2 开始引入。它是 List 接口的可调整大小的数组实现。实现所有可选的列表操作,并允许所有元素,包括 null 。 除了实现 List 接口之外,此类还提供一些方法来操纵内部用于存储列表的数组的大小。此类与 Vector 大致等效,但它是不同步的。
size,isEmpty,get, set ,iterator 和 listIterator 操作在恒定时间内运行。add 操作在“摊销固定时间”中运行,也就是说,添加n个元素需要O(n)时间。所有其他操作均以线性时间运行(大致而言)。 与 LinkedList 实现的常量因子相比,常量因子较低。
每个 ArrayList 实例都有一个“容量”。 容量是用于在列表中存储元素的数组的大小。 它总是至少与列表大小一样大。 将元素添加到ArrayList后,其容量会自动增长。 除了添加元素具有固定的摊销时间成本外,没有指定增长策略的详细信息。在操作添加大量元素之前,应用程序可以使用 ensureCapacity 增加ArrayList 实例的容量,这可以减少增量重新分配的数量。
请注意,此实现未同步。如果多个线程同时访问 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则它必须在外部进行同步。 (结构修改是添加或删除一个或多个元素,或显式调整底层数组的大小的任何操作;仅设置元素的值不是结构修改。)这通常是通过同步某些自然封装列表的对象来实现的 。如果不存在这样的对象,则应使用 Collections.synchronizedList 方法“包装”列表。 最好在创建时完成此操作,以防止意外地不同步访问列表:
List list = Collections.synchronizedList(new ArrayList(...));
此类的 iterator 迭代器和 listIterator 方法返回的迭代器是快速失败:如果在创建迭代器之后的任何时间对列表进行结构修改,除了通过迭代器自己的ListIterator#remove()或 ListIterator#add(Object)的其他方法,迭代器将抛出{@link ConcurrentModificationException}。 因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间内冒任意、不确定的行为的风险。请注意,迭代器的快速失败行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。 快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。 因此,编写依赖于此异常的程序来确保程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
2. 常用方法
2.1 构造方法
最常用的就是空参构造方法,看一下源码实现:
/** * 构造一个初始容量为10的空列表 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }/** * 共享的空数组实例,用于默认大小的空实例。 * 我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要扩充多少。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/** * 存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度。 * 添加第一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * 的空ArrayList都将扩展为 DEFAULT_CAPACITY。 */ transient Object[] elementData; // non-private to simplify nested class access 非私有以简化嵌套类访问
构造方法注释:构造一个初始容量为10的空列表,但是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与 elementData 都不满足,elementData 变量注释有这样一个描述:添加第一个元素时,任何具有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空ArrayList都将扩展为 DEFAULT_CAPACITY。查看添加方法最终会调用如下方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//注意内部数组对象是默认空数组实例 return Math.max(DEFAULT_CAPACITY, minCapacity);//取较大者,minCapacity在第一次添加是为 1 } return minCapacity; }/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //此时 elementData.length = 0 grow(minCapacity); }
由上可知,无参构造方法创建的数组列表实例,底层数组的 length = 0,在添加第一个元素时会扩充数组容量(具体逻辑先不深入)为 10,此时这个列表的容量也就是 10。
可指定初始化容量的构造方法:
/** * 构造一个具有指定初始容量的空列表。 */ 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); } }/** * 用于空实例的共享空数组实例。 */ private static final Object[] EMPTY_ELEMENTDATA = {};
初始化容量大于0,直接创建一个指定大小的数组对象并赋值给底层数组变量;初始化容量等于0 时,使用共享空数组实例给底层数组变量赋值,注意,此时添加第一个元素时不会把容量扩充为10,只有内部数组是默认空数组实例才会把容量初始化为10。
包含指定集合的构造方法
/** * 构造一个列表,该列表包含指定集合的元素,其顺序由集合的迭代器返回。 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray();//集合转数组,不同集合接口子类实现方式不同 if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) 存在数组类型不是 Object[] 的情况 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array.空数组使用共享空数组实例 this.elementData = EMPTY_ELEMENTDATA; } }
有代码可知,传入的集合对象转换的数组对象,如果该数组对象大小等于0,该列表内部数组变量赋值为共享空数组实例,否则,该列表内部数组变量赋值为转换的数组对象,而且保证类型为 Object[]
顺序和迭代器的关系还没搞明白。
2.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)); }private static int calculateCapacity(Object[] elementData, int minCapacity) {//计算容量,主要是将空参构造方法创建的列表初始化容量设置为10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }private void ensureExplicitCapacity(int minCapacity) {//确定一个精确的容量,最小容量大于内部数组大小时要扩容 modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }/** * 增加容量以确保它至少可以容纳最小容量参数指定的元素数量。 * 增量为原来大小的一半 * 如果增加后的新容量小于期望的最小容量,则新容量值应该为最小容量 * 如果增加后的新容量大于常量最大数组大小,则调用巨大容量方法返回值作为新容量值 */ 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); }private static int hugeCapacity(int minCapacity) {若期望的最小容量大于常量最大数组大小,则新容量为最大整型值,否则,新容量为常量最大数组大小 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }/** * 要分配的最大数组大小。 一些虚拟机在数组中保留一些头字。 * 尝试分配更大的阵列可能会导致OutOfMemoryError:请求的阵列大小超出VM限制 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
在指定位置添加一个指定元素
/** * 将指定的元素插入此列表中的指定位置。 * 将当前在该位置的元素(如果有)和任何后续元素右移(将其索引加1)。 * @throws IndexOutOfBoundsException {@inheritDoc} */ 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++; }/** * add和addAll使用的rangeCheck版本。 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
添加指定集合的所有元素
/** * 按照指定集合的Iterator返回的顺序,将指定集合中的所有元素追加到此列表的末尾。 * 如果在操作进行过程中修改了指定的集合,则此操作的行为是不确定的。 *(这意味着如果指定的集合是此列表,并且此列表是非空的,则此调用的行为是不确定的。) * @return <tt>true</tt> if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); //异常会在此方法抛出 size += numNew; return numNew != 0;//调用后列表发生变化返回true }
在指定位置添加指定集合
/** * 从指定位置开始,将指定集合中的所有元素插入此列表。 * 将当前位于该位置的元素(如果有)和任何后续元素右移(增加其索引)。 * 新元素将按照指定集合的迭代器返回的顺序显示在列表中。 * @return <tt>true</tt> if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ 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) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
有个疑问,在addAll 方法中,指定的集合转换成成数组后默认是 Object[] 类型,和构造方法中的指定集合转成数组有些不同,没有考虑转换的数组存在类型不是 Object [] 的可能,这个暂时没搞明白。
2.3 查询操作
这里先看一下根据指定下标查询元素,根据指定元素查询下标这两类方法,迭代器查询后面单独了解。
根据下标查询
/** * 返回此列表中指定位置的元素。 * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { rangeCheck(index); return elementData(index); }/** * 检查给定的索引是否在范围内。 如果不是,则抛出适当的运行时异常 * 此方法不检查索引是否为负:始终在数组访问之前立即使用它,如果索引为负, * 则抛出 ArrayIndexOutOfBoundsException。 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
根据元素查询
/** * 返回指定元素在此列表中首次出现的索引;如果此列表不包含该元素,则返回-1。 * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ 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; } //在查找下标时,从0 开始由小到大遍历,返回首次出现的下标值;对 null 值使用 == 判断是否是指定对象,其他对象使用 equals 方法来判断是否是同一个对象。/** * 返回指定元素在此列表中最后一次出现的索引;如果此列表不包含该元素,则返回-1。 * More formally, returns the highest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ 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; }在查找下标时,从size-1 开始由大到小遍历,返回首次出现的下标值;/** * 如果此列表包含指定的元素,则返回 true 。 * More formally, returns <tt>true</tt> if and only if this list contains * at least one element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. */ public boolean contains(Object o) { return indexOf(o) >= 0; //调用 indexOf 方法,根据返回值是否大于等于0 来判断是否包含指定元素,不包含时 indexOf 返回 -1 小于 0 返回 false }
2.4 修改操作
/** * 用指定的元素替换此列表中指定位置的元素。返回被替换的元素 * @throws IndexOutOfBoundsException {@inheritDoc} */ public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
2.5 删除操作
删除指定位置元素
/** * 删除此列表中指定位置的元素。 将所有后续元素向左移动(其索引减1)。 * 返回被删除的元素 * @throws IndexOutOfBoundsException {@inheritDoc} */ 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; // 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; }/* * 私有 remove 方法,跳过边界检查并且不返回已删除的值。 */ 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 }
该方法那边调用了私有的删除方法 fastRemove(int index) ,该方法与删除指定位置元素的方法区别就是 不需边界校验(遍历调用),也不需返回已删除的值,其他逻辑一致。
清空列表
/** * 从此列表中删除所有元素。 该调用返回后,该列表将为空。 */ public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }//内部数组每个位置都置为 null,但是数组大小没有改变;列表大小置为 0
删除指定范围的元素
/** * 从此列表中删除索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。 * 将所有后续元素向左移动(减少其索引)。 此调用通过(toIndex-fromIndex)元素来缩短列表。 * (如果 toIndex == fromIndex},则此操作无效。) * * @throws IndexOutOfBoundsException if {@code fromIndex} or * {@code toIndex} is out of range * ({@code fromIndex < 0 || * fromIndex >= size() || * toIndex > size() || * toIndex < fromIndex}) */ protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; 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; } size = newSize; }
删除包含的指定集合元素
/** * 从此列表中删除指定集合中包含的所有元素。 * 因调用此方法使列表改变就返回true * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (<a href="Collection.html#optional-restrictions">optional</a>) * @throws NullPointerException if this list contains a null element and the * specified collection does not permit null elements 疑问 1 * (<a href="Collection.html#optional-restrictions">optional</a>), * or if the specified collection is null * @see Collection#contains(Object) */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); //检查指定集合是否为空 return batchRemove(c, false); }public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++)//遍历数组 if (c.contains(elementData[r]) == complement) // 此处解答 疑问 1 elementData[w++] = elementData[r];// 存放指定集合不包含的元素(complement = false) } finally { // Preserve behavioral compatibility with AbstractCollection, 疑问 2,哪种行为? // even if c.contains() throws. if (r != size) {//c.contains() 出现异常跳出循环才会符合条件 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) {//满足条件表示有元素包含在指定集合中,要删除,列表发生改变 // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
来源:oschina
链接:https://my.oschina.net/u/3488841/blog/4293032