Java集合框架成员之ArrayList类的源码分析(基于JDK1.8版本)

前言:
加深对Java的学习与理解的最好途径之一,就是在掌握了Java的基础知识之后,对JDK中的源码进行学习和分析;通过分析牛人们的代码,可以学到很多东西!这些知识和经验对于巩固加深对Java的掌握来说,大有裨益!

在分析源码之前,先概述性地列出ArrayList类的一些注意点:

  • ArrayList类是List接口的基于可变数组的实现类,实现了List接口提供的所有操作,并允许添加包含null元素在内的任意元素;除了实现了List接口中的所有方法,ArrayList类还提供了供内部使用的用以调节底层数组大小从而存放线性表中的元素的方法(扩容、缩容方法);
  • ArrayList类与Vector类大致上相同,除了前者是非线程安全的,后者是线程安全的;
  • get、set、size、isEmpty、 iterator、listIterator等方法的时间复杂度为O(1);
  • 在添加大量元素之前,可以通过调用ensureCapacity方法来增加ArrayList对象的容量,从而减少增量扩容操作的次数;
  • 结构上的调整指的是:添加或删除一个或多个元素,或者明确调整底层数组的大小,仅仅设置一个元素的值并不是结构上的调整;
  • 如果多个线程同时对一个ArrayList对象进行并发访问,并且至少有一个线程对其进行结构上的调整,那么必须在外部对其进行同步处理,可以采用synchronized或者ReentrantLock对其进行同步,或者调用Collections工具类的静态方法synchronizedList对该ArrayList对象进行包装(此操作最好在该ArrayList对象创建时进行,以避免对其进行的意外非同步访问),如List list = Collections.synchronizedList(new ArrayList(…));
  • ArrayList类的迭代操作是fail-fast的;
  • ArrayList类是Java集合框架下的一个成员;

下面对基于JDK1.8版本中的ArrayList类的源码进行分析:
首先,看一下类的声明部分:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

从类的声明来看,ArrayList类继承于AbstractList类,同时实现了List、RandomAccess、Cloneable和java.io.Serializable接口;

其中AbstractList类是一个实现了List接口的抽象类,其中实现了部分List接口中的方法,List接口中提供了列表所需要的方法;RandomAccess接口是一种标记接口,其中并没有方法;Cloneable接口为其实现类提供浅拷贝的功能,即实现了该接口的类的对象可以对其自身进行拷贝;实现了java.io.Serializable接口的类,其对象可以进行序列化,即可以持久化该实现类的对象;

以下是ArrayList类的数据成员:

 /** * 默认的存储容量(静态常量,其值不可更改,且由所有实例对象所共享). */ private static final int DEFAULT_CAPACITY = 10; /** * 空的实例可以共享的空的数组对象(静态常量,其值不可更改,且由所有实例对象所共享). */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 创建默认大小的空的实例时,作为该实例的底层数组 * 我们通过当第一个元素被添加到对象中时,该数组的尺寸的增大范围来区分其与EMPTY_ELEMENTDATA数组 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 用于存储ArrayList对象中的元素的数组 * ArrayList对象的容量是这个数组的长度 * 任何底层数组elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空的ArrayList对象 * 当第一个元素被添加到该对象中时,底层数组都会被扩展到默认容量大小,即长度为10的数组; */ transient Object[] elementData; // 非私有简化内部类的访问 /** * ArrayList对象中元素的个数,即该对象的大小 * @serial */ private int size; 

从上面可以看出,ArrayList类中提供了默认初始化容量 DEFAULT_CAPACITY;elementData数组用于作为底层存储结构,存放ArrayList中的元素;size用于记录ArrayList对象中已存放的元素个数;

因此,ArrayList类的底层是采用数组这种存储结构来实现的!所以,对ArrayList对象所存放的对象的操作基本上都可以看作是对数组中元素的操作。

以下是ArrayList类中的构造器:

 /** * 创建一个空列表,其容量由参数initialCapacity决定 * @param initialCapacity 列表的初始容量 */ 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); } } /** * 创建一个空列表,其初始容量为10,当添加第一个元素时,才会将底层数组变为长度为10的数组. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 创建一个包含集合c中所有元素的ArrayList对象, * 元素的存放顺序由集合参数的迭代器返回其元素的顺序决定. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } 

ArrayList类一共提供了三个构造函数,这里的三个构造函数属于构造器的重载;
第一个含参构造器中的参数initialCapacity指定了创建的对象中的申请的底层数组长度;
第二个无参构造器创建了一个初始容量为10的空列表,但是并不会立即开辟空间,而是在第一次添加元素时进行数组的创建;
第三个含参构造器中的参数为一个集合,通过此构造器将会创建一个包含该集合中所有元素的ArrayList对象,元素的存放顺序由集合参数的迭代器返回其元素的顺序决定;

以下是ArrayList类中提供的用于优化该类对象中所占的内存空间,即当确定该ArrayList对象不会再添加元素时,通过trimToSize方法,将底层数组进行相应的替换从而节省未使用的内存空间;

 /** * 将当前ArrayList对象的容量压缩为实际包含元素的个数. * 应用程序可以通过此方法最小化ArrayList对象所占的内存. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } 

通过分析trimToSize方法,我们可以看出,ArrayList类提供了用于优化其对象所占内存的方法:当ArrayList对象中的底层数组没有完全使用完时,通过此方法将底层数组中的元素全部复制到一个长度为当前ArrayList对象中元素个数的数组,并用此数组替换原先的底层数组;从而避免了内存浪费;由于此方法对底层数组进行了结构上的调整,因此需要将modCount自增;

以下列出的方法都是与添加元素有关的,同时,也可以说是造就ArrayList可以存放数量可变的元素的根本因素;

 /** * 如果需要的话,增大当前ArrayList对象的容量,从而确保其可以存放至少数量为minCapacity的元素. * @param minCapacity 期望的最小容量 */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } /** * 可以分配到的最大数组长度 * 一些虚拟机在一个数组中保留一些头部字. * 试图分配更大的数组会导致内存溢出错误:所需的数组大小超过了虚拟机的限制 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 增加容量以确保当前ArrayList对象可以存放下由个数为参数minimum的元素. * @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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } 

上述的五个方法,都或多或少直接与扩容相关,故将其罗列出来进行分析;

ensureCapacity方法是公有方法,因此该方法可供外部调用,从而在实际情况中,需要增添大量元素之前,通过调用该方法来手动增加ArrayList实例的容量,从而减少递增式再分配的次数,从而提高ArrayList对象的存储效率;

ensureCapacity用于如果有必要时,将会扩大底层数组容量从而能够存放数量为指定参数minCapacity的元素;下面详细分析此方法的作用(以下分析皆是基于minCapacity大于零的前提下进行的,因为minCapacity小于等于零时,根本就不会通过ensureCapacity方法和ensureExplicitCapacity方法中的if判断条件,那么也就不会进行后续的扩容操作,也就不会扩容):

add(E)的详细流程:
首先,判断底层数组是否为本类中定义的默认数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此数组对应的默认容量为10,如果不是,初始容量值为其对应的实际长度;通过此判断,得出最小扩展量minExpand;接着,比较参数minCapacity和minExpand的大小,从而决定是否有可能需要调用ensureExplicitCapacity方法,对底层数组进行扩容;如果要添加的元素数量大于minExpand,说明可能需要扩容,则调用ensureExplicitCapacity方法进行扩容;否则不调用,即不需要扩容;

若需要扩容,则进入ensureExplicitCapacity方法,此方法首先将modCount变量进行自增,此变量用于标记对ArrayList对象进行结构上修改的次数;然后通过minCapacity – elementData.length > 0进行判断,是否需要进行扩容;此处一旦通过判断进入grow方法,那么就一定会进行扩容,即grow方法才是真正地用来进行扩容的方法,先前的方法都是用于判断是否需要扩容;

下面是对grow方法的具体内容进行分析:

 // 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); 

首先,将当前底层数组的长度存放到变量oldCapacity,oldCapacity就是扩容前的数组长度,然后,通过右移操作求出扩容前的底层数组长度oldCapacity的一半,再加上oldCapacity,即得到的值为oldCapacity的1.5倍,将其存放到变量newCapacity中,newCapacity就是扩容后的数组长度(不过这个值是否为最终底层数组的长度还需要看后面的判断);

因为接下来有两个if判断语句,所以下面将需要分四种情况进行分析:

第一种情况:①和②处的if条件都成立;
如果①处的if条件成立,即1.5倍的oldCapacity比minCapacity小,那么说明当前的新容量不足以存放指定参数对应的的元素个数,还需要再进行扩容,即更新newCapacity为minCapacity;此时,程序执行到②处的if条件,由于此处的条件也成立,即newCapacity大于MAX_ARRAY_SIZE,也就是minCapacity大于MAX_ARRAY_SIZE(默认数组最大长度),那么还需要更新newCapacity,即通过hugeCapacity方法将当前的新容量进行处理,通过此方法的最后的一条语句可知,如果minCapacity>MAX_ARRAY_SIZE成立,则返回Integer.MAX_VALUE ,否则返回MAX_ARRAY_SIZE(即Integer.MAX_VALUE-8);此时的newCapacity为最终扩容后新容量,然后通过Arrays.copyOf方法将ArrayList中的元素放入新的数组(数组长度为最新的newCapacity)中,并将原底层数组替换为这个新的数组,完成扩容!

第二种情况:①处的if条件成立,②处的if条件不成立;
如果①处的条件成立,则根据第一种情况可知,newCapacity为minCapacity;②处的条件不成立,则newCapacity不大于MAX_ARRAY_SIZE,那么就不需要hugeCapacity方法进一步处理,直接调用Arrays.copyOf方法,将ArrayList中的元素放入新的数组(数组长度为最新的newCapacity)中,并将原底层数组替换为这个新的数组,完成扩容!

第三种情况:①处的if条件不成立,②处的if条件成立;
如果①处的条件不成立,即1.5倍的oldCapacity不比minCapacity小,说明1.5倍的oldCapacity足以存放数量为minCapacity的元素,那么只需要进行②处的比较,如果newCapacity大于MAX_ARRAY_SIZE,那么说明新容量超过了默认的最大数组容量,那么将会对新容量进行处理(即减小新容量),使其不超过合法的数组长度,如果minCapacity>MAX_ARRAY_SIZE成立,则返回Integer.MAX_VALUE ,否则返回MAX_ARRAY_SIZE(即Integer.MAX_VALUE-8);此时的newCapacity为最终扩容后新容量,然后通过Arrays.copyOf方法将ArrayList中的元素放入新的数组(数组长度为最新的newCapacity)中,并将原底层数组替换为这个新的数组,完成扩容!

第四种情况:①处的if条件不成立,②处的if条件不成立;
如果①处的条件不成立,即1.5倍的oldCapacity不比minCapacity小,说明1.5倍的oldCapacity足以存放数量为minCapacity的元素,那么只需要进行②处的比较;如果②处的条件不成立,则newCapacity不大于MAX_ARRAY_SIZE,那么就不需要hugeCapacity方法进一步处理,直接调用Arrays.copyOf方法,将ArrayList中的元素放入新的数组(数组长度为最新的newCapacity)中,并将原底层数组替换为这个新的数组,完成扩容!
总的来说,第一个if语句作用是判断1.5倍的旧容量是否足够存放数量为minCapacity的元素,并根据判断结果决定是否再次更新新容量;第二if语句作用是判断足以存放数量为minCapacity的元素的新容量newCapacity是否超出了默认的最大数组长度,如果超出就对其进行缩小,然后进行元素的拷贝和数组的替换工作,否则直接进行元素的拷贝和数组的替换工作!

以上就是对主要的扩容处理方法的分析,还有一个ensureCapacityInternal方法上面没有说到,该方法是私有方法(ensureCapacity方法是公有方法),即供类中其他方法进行调用的方法,外部不可访问,这一点在后面的添加元素方法中可以看到;并且通过该方法中的语句可以知道,ensureCapacityInternal与ensureCapacity的区别在于,调用此方法则必定会调用ensureExplicitCapacity方法,从而导致modCount进行自增,而ensureCapacity方法则不一定会这样!这两个方法的作用都是进行扩容所必需的前期工作!

通过观察上面方法的实现,可以看出源码的作者可以说是考虑很周全,在多次判断之后,将扩容的最终容量进行确定下来,此时,新的容量才是最合适也是最合理的!

以下方法是一些简单的列表方法:

 // 返回列表中元素的个数. public int size() { return size; } // 判断当前对象中是否不包含元素. public boolean isEmpty() { return size == 0; } // 如果当前列表包含指定元素,则返回true.更普遍的是,当且仅当此列表中存在一个或多个元素 //通过o==null?e==null:o.equals(e))返回true,此方法就返回true; public boolean contains(Object o) { 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; } /** * 同上,不过查找的方向是从后向前. */ 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()方法直接返回当前ArrayList对象中已存放的元素个数;

isEmpty()方法判断当前ArrayList对象中是否无元素;

contains方法用于判断当前ArrayList对象中是否存在于指定参数相同的元素,内部是通过indexOf方法来进行元素查找匹配的工作;

indexOf方法用于查找当前ArrayList对象中索引最小的与指定参数相同的元素,并返回其索引位置,如果不存在这样的元素,则返回-1,这里需要注意的是:由于ArrayList对象中允许存放null元素,故在判定一个元素与指定参数是否相等时,需要注意null元素的比较;

lastIndexOf方法用于查找当前ArrayList对象中索引最大的与指定参数相同的元素,并返回其索引位置,如果不存在这样的元素,则返回-1;

 /** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } 

此方法用于将当前ArrayList对象进行浅拷贝,并返回备份;这里需说明的是:浅拷贝是针对拷贝的对象中包含抽象数据类型的数据成员而言的,这里v是ArrayList对象,其中存放着与当前被拷贝的ArrayList对象相同的元素的引用;深拷贝指的是,将元素直接进行拷贝,得到另一个元素(即需要为其开辟内存空间,与原来的元素占据不同的内存),而不是仅仅拷贝其引用;

 public Object[] toArray() { return Arrays.copyOf(elementData, size); } 

此方法返回一个新的数组,该数组包含当前ArrayList对象中elementData数组中的所有元素,并且返回的数组长度正好为元素的个数;

以下方法是基于索引位置的操作:

 // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** * Returns the element at the specified position in this list. */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * Replaces the element at the specified position in this list with * the specified element. */ public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } /** * Checks if the given index is in range. If not, throws an appropriate * runtime exception. This method does *not* check if the index is * negative: It is always used immediately prior to an array access, * which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } 

elementData方法由缺省修饰符修饰,供内部调用,返回数组elementData在指定索引处的元素;

get方法返回列表中指定索引处的元素,注意,在方法内部调用了rangeCheck私有方法来对索引参数的合法性进行判定,如果当前索引大于等于当前列表中元素个数,说明该索引无效,并且抛出下标越界异常;

set方法将指定索引处的元素替换为指定对象,同样地,在方法内部调用了rangeCheck私有方法来对索引参数的合法性进行判定;

rangeCheck方法对索引参数的合法性进行判定,如果当前索引大于等于当前列表中元素个数,说明该索引无效,并且抛出下标越界异常;

以下方法是对列表中元素进行增删操作:

 // 在当前列表对象的尾部追加指定元素. public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * 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). */ 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++; } /** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). */ 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; } /** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ 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 remove method that skips bounds checking and does not * return the value removed. */ 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 } /** * Removes all of the elements from this list. The list will * be empty after this call returns. */ public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the * specified collection's Iterator. The behavior of this operation is * undefined if the specified collection is modified while the operation * is in progress. (This implies that the behavior of this call is * undefined if the specified collection is this list, and this * list is nonempty.) */ 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; } /** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. */ 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; } /** * Removes from this list all of the elements whose index is between * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * Shifts any succeeding elements to the left (reduces their index). * This call shortens the list by {@code (toIndex - fromIndex)} elements. * (If {@code toIndex==fromIndex}, this operation has no effect.) * * @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; } /** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } 

add(E e)方法:首先,该方法是在列表的尾部进行插入元素;其次,既然是添加元素,那么就属于JDK中说明的结构上的调整操作,则需要对modCount进行自增操作,同时,需要通过调用ensureCapacityInternal方法来确保底层数组能够存放待插入的元素,当前元素个数为size,那么插入元素e之后的个数为size+1,那么就需要判断当前底层数组长度是否大于等于size+1,如果小于,则先进行扩容,再插入元素e;否则,不需要扩容,直接插入!并将元素个数size加一;

add(int index, E element)方法:相比上一方法,此方法只允许在指定的索引处进行元素的添加,那么就需要调用私有方法rangeCheckForAdd来判定指定的索引处是否可以插入元素element;如果index > size || index < 0成立,那么说明插入的位置不合理,因为列表是连续存放元素的,不允许元素之间存在空隙;接下来,需要对当前数组容量是否能够容纳元素element进行判断,必要时进行扩容;最后,由于此方法是在指定索引处添加元素,因此需要对该位置及之后的元素进行向右移位;最后才可以插入元素并将元素个数size加一;

remove(int index)方法:删除指定位置处的元素;删除元素也属于结构上调整的操作,所以,需要modCount自增;首先,检查索引的合法性;接着,该索引处之后的元素依次向前平移,最后将索引为size-1处的数组元素置为null,同时对size进行减一;这里值得我们借鉴的地方是将size-1处的元素置为null,而不是对其不管不问,这样有利于虚拟机进行无用内存的回收,从而有效地利用内存空间!

其他的一些操作,基本上,都是在之前方法的基础上增加了一些条件,但是最基本也是最重要的关键点,都相同,故不再赘述;

综上所述,读完ArrayList类源码之后,第一感受是,源码的创作者的思维十分地缜密,能够注意到许多容易忽略的细节,并相应地作出优化后的处理;这一点值得所有程序员学习!源码中还有很多值得学习的地方,越深入地理解,可以学习的地方就越多!!!

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/boker_han/article/details/79038116
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞