一、概述

ArrayList是经常用到的一个容器,它是Java Collections Framework的一个成员,然后底层是基于定长数组来实现的,如果数组存放满了,就会通过扩容机制重新生成一个更大的数组来存放数据。

因此,扩容机制是ArrayList的核心所在,这一点是务必要掌握的;除此之外,本文还会叙述ArrayList的基本操作是怎样实现的,以及其它的细节。

二、ArrayList源码解读

2.1、继承关系

UML类图中可以看到,ArrayList直接或间接实现了IterableCollectionListRandomAccessCloneableSerializable这6个接口;ArrayList直接或间接继承了AbstractListAbstractCollection这2个抽象类。

疑问来了:为什么AbstractList实现了List接口,ArrayList还要去再实现一次List接口,是吃饱了撑着吗?

带着疑问,我立马去网上冲浪一波,然后发现了3个对此的解释。

观点1:如果ArrayList没去实现List接口,会导致classgetInterfaces方法返回不同的结果,这么做是为了方便基于List接口的动态代理。

观点2:这可能是为了增加继承结构的可追踪性。当浏览Javadoc或类似的东西时,就不必遍历整个继承树,它不会有任何不良影响,并且可以帮助理解代码。

观点3:ArrayList的作者Josh Bloch曾经认为ArrayList再实现一次List接口是有一些价值的,但后来发现这是个错误。

以上3个观点均出自stackoverflow的一个帖子,我们了解下就好了,深究意义不大,具体原因还得是作者自己才知道。

2.1.1、Iterable接口

2.1.1.1、概述

Iterable表示可迭代的,它有个iterator方法,需要返回Iterator对象,Iterator是一个接口,表示为迭代器。Iterable接口的定义如下:

1
2
3
4
5
6
public interface Iterable<T> {

    Iterator<T> iterator();

    // .....省略
}

那么实现Iterable接口有什么作用呢?先说结论:只要对象实现了Iterable接口,就可以使用for-each语法,编译器会转换为调用Iterable和Iterator接口的方法。

我们平常用for-each语法遍历list时,你可能会写下如下代码:

1
2
3
4
5
6
7
List<Integer> list = new ArrayList<>(3);
list.add(1);
list.add(2);
list.add(3);
for (Integer value : list) {
    System.out.println("value: " + value);
}

这种for-each语法背后是怎么实现的呢?其实,编译器会将它转换为类似如下代码:

1
2
3
4
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println("value: " + iterator.next());
}

除了Iterable接口的iterator方法外,List接口也有一个listIterator方法,需要返回ListIterator对象,ListIterator是一个接口,它对Iterator接口做了扩展。比如,可以从末尾往前遍历,如下代码:

1
2
3
4
ListIterator<Integer> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()){
    System.out.println("value: " + iterator.previous());
}

2.1.1.2、fail-fast机制

那些年,年少时所犯下的错误,想起自己刚入门Java时,曾经写过如下代码:

1
2
3
4
5
for (Integer value : list) {
    if (value < 3) {
        list.remove(value);
    }
}

当年的直觉告诉你,这么写不存在问题,但是运行却会报并发修改的错误:

1
Exception in thread "main" java.util.ConcurrentModificationException

显然,这里是没有并发的代码,我们去看看ConcurrentModificationException的注释描述。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
 * 当不允许进行此类修改时,已检测到对象的并发修改的方法可能会抛出此异常。
 * This exception may be thrown by methods that have detected concurrent
 * modification of an object when such modification is not permissible.
 * <p>
 * 例如,通常不允许一个线程在另一个线程迭代集合时修改集合。
 * For example, it is not generally permissible for one thread to modify a Collection
 * while another thread is iterating over it.  
 * 通常,在这些情况下迭代的结果是不确定的。
 * In general, the results of the iteration are undefined under these circumstances. 
 * 如果检测到此行为,某些 Iterator 实现(包括 JRE 提供的所有通用集合实现)可能会选择抛出此异常。
 * Some Iterator implementations (including those of all the general purpose collection implementations
 * provided by the JRE) may choose to throw this exception if this behavior is
 * detected.  
 * 执行此操作的迭代器被称为fail-fast迭代器,因为它们快速而干净地失败,而不是冒着在未来不确定的时间出现任意的、不确定的行为的风险。
 * Iterators that do this are known as <i>fail-fast</i> iterators,
 * as they fail quickly and cleanly, rather that risking arbitrary,
 * non-deterministic behavior at an undetermined time in the future.
 * <p>
 * 请注意,此异常并不总是表示对象已被不同的线程并发修改。
 * Note that this exception does not always indicate that an object has
 * been concurrently modified by a <i>different</i> thread.  
 * 如果单个线程发出一系列违反对象契约的方法调用,则该对象可能会抛出此异常。
 * If a single thread issues a sequence of method invocations that violates the
 * contract of an object, the object may throw this exception. 
 * 例如,如果线程在使用fail-fast迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。
 * For example, if a thread modifies a collection directly while it is
 * iterating over the collection with a fail-fast iterator, the iterator
 * will throw this exception.
 * 请注意,不能保证fail-fast行为,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬性保证。
 * <p>Note that fail-fast behavior cannot be guaranteed as it is, generally
 * speaking, impossible to make any hard guarantees in the presence of
 * unsynchronized concurrent modification. 
 * ail-fast操作会尽最大努力抛出ConcurrentModificationException。
 * Fail-fast operations throw {@code ConcurrentModificationException} on a best-effort basis.
 * 因此,编写依赖于此异常的正确性的程序是错误的: ConcurrentModificationException应该仅用于检测错误。
 * Therefore, it would be wrong to write a program that depended on this
 * exception for its correctness: <i>{@code ConcurrentModificationException}
 * should be used only to detect bugs.</i>
 *
 * @author  Josh Bloch
 * @see     Collection
 * @see     Iterator
 * @see     Spliterator
 * @see     ListIterator
 * @see     Vector
 * @see     LinkedList
 * @see     HashSet
 * @see     Hashtable
 * @see     TreeMap
 * @see     AbstractList
 * @since   1.2
 */

上面的注释说的很明显了,如果线程在使用fail-fast迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

那么如何避免异常呢?可以使用迭代器的remove方法,如下所示:

1
2
3
4
5
6
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
    if (iterator.next() < 3) {
        iterator.remove();
    }
}

它自己的remove方法为何又可以使用呢?我们需要看下迭代器的原理了。

2.1.1.3、迭代器的原理

我们先来看下ArrayListiterator方法的实现,代码如下:

1
2
3
4
5
6
7
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Iterator<E> iterator() {
        return new Itr();
    }
}

iterator方法实现很简单,直接new一个Itr对象返回,ItrArrayList的一个成员内部类,实现了Iterator接口,它的代码量并不多,所以直接贴出来了,看注释应该就能明白。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return  下一个要返回的元素位置
    int lastRet = -1; // index of last element returned; -1 if no such  最后一个返回的索引位置,如果没有,为-1
    int expectedModCount = modCount;  // 期望的修改次数,初始化为外部类当前的修改次数modCount

    // prevent creating a synthetic constructor
    Itr() {}

    public boolean hasNext() {
        return cursor != size;     // cursor与数组元素数量的比较
    }

    @SuppressWarnings("unchecked")
    public E next() {
        // 校验是否发生了结构性变化
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 更新cursor的值,每次+1
        cursor = i + 1;
        // 更新lastRet值,返回对应的元素
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        // 注意:lastRet < 0时会抛出异常,所以调用remove()之前需要先调用next()去更新lastRet的值
        if (lastRet < 0)
            throw new IllegalStateException();
        // 校验是否发生了结构性变化
        checkForComodification();

        try {
            // 执行ArrayList的remove方法,modCount++
            ArrayList.this.remove(lastRet);
            // 更新cursor的值,remove后元素数量少了一个,相当于cursor=cursor-1
            cursor = lastRet;
            // 重置lastRet
            lastRet = -1;
            // 更新expectedModCount
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    // 校验是否发生了结构性变化,所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

迭代器源码中next方法,remove方法都会调用checkForComodification方法进行校验是否发生了结构性变化,由此可见,迭代器的内部维护了索引位置相关的数据,要求在迭代过程中,不能发生结构性变化,否则这些索引位置功能就会失效。

2.1.2、RandomAccess接口

RandomAccess内部是没有任何代码的接口,它属于标记接口,其定义如下:

1
2
public interface RandomAccess {
}

实现了RandomAccess接口的类表示支持快速随机访问,用在一些通用的算法代码中,它可以根据这个声明而选择效率更高的实现,比如Collections类的binarySearch方法,会根据List是否实现了RandomAccess接口而采用不同的实现,代码如下:

1
2
3
4
5
6
7
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
}

2.2、成员变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 默认初始容量
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 都将扩展为 DEFAULT_CAPACITY。
transient Object[] elementData; 

// ArrayList的大小(它包含的元素数)
private int size;

// 要分配的数组的最大大小(除非必要)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  

// 此列表在结构上被修改的次数。结构修改是那些改变列表大小的修改,或者以其他方式扰乱列表,使得正在进行的迭代可能会产生不正确的结果。
protected transient int modCount = 0;  

疑问来了:我们知道了ArrayList实现了Serializable接口,但是elementData为何要用transient修饰,这不表示elementData不能被序列化?

其实玄机在于ArrayList中的两个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 将ArrayList实例的状态保存到流中(即序列化它)。
private void writeObject(java.io.ObjectOutputStream s)                      
        throws java.io.IOException {                                        
    // Write out element count, and any hidden stuff                        
    int expectedModCount = modCount;                                        
    s.defaultWriteObject();                                                 

    // Write out size as capacity for behavioral compatibility with clone() 
    s.writeInt(size);                                                       

    // Write out all elements in the proper order.                          
    for (int i = 0; i < size; i++) {                                        
        s.writeObject(elementData[i]);                                      
    }                                                                       

    if (modCount != expectedModCount) {                                     
        throw new ConcurrentModificationException();                        
    }                                                                       
} 

// 从流中重构ArrayList实例(即反序列化)
private void readObject(java.io.ObjectInputStream s)                                       
    throws java.io.IOException, ClassNotFoundException {                                   

    // Read in size, and any hidden stuff                                                  
    s.defaultReadObject();                                                                 

    // Read in capacity                                                                    
    s.readInt(); // ignored                                                                

    if (size > 0) {                                                                        
        // like clone(), allocate array based upon size not capacity                       
        SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
        Object[] elements = new Object[size];                                              

        // Read in all elements in the proper order.                                       
        for (int i = 0; i < size; i++) {                                                   
            elements[i] = s.readObject();                                                  
        }                                                                                  

        elementData = elements;                                                            
    } else if (size == 0) {                                                                
        elementData = EMPTY_ELEMENTDATA;                                                   
    } else {                                                                               
        throw new java.io.InvalidObjectException("Invalid size: " + size);                 
    }                                                                                      
}                                                                                                                                                                                                                                                 

ArrayList在序列化的时候会调用writeObject方法,直接将sizeelement写入ObjectOutputStream;反序列化时调用readObject方法,从ObjectInputStream获取sizeelement,再恢复到elementData

elementData是存储ArrayList元素的数组缓冲区,通常扩容后都会预留一些空间,也就是说有部分空间实际没有存储元素,序列化时只序列化实际存储的那些元素,而不是整个数组,从而可以节省空间和时间。

疑问来了:为什么MAX_ARRAY_SIZE的值是Integer.MAX_VALUE - 8而不是Integer.MAX_VALUE?

因为存储了Array的头部信息,所以这里需要减去8。

2.3、构造方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 空参构造方法
public ArrayList() {    
    // 由于没有指定初始容量,所以赋值一个空实例的共享空数组实例,可以理解为标记调空参数构造方法                           
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}            

// 指定了初始容量的构造方法
public ArrayList(int initialCapacity) {                                                  
    if (initialCapacity > 0) { 
        // 创建指定大小的Object数组                                                          
        this.elementData = new Object[initialCapacity];                                  
    } else if (initialCapacity == 0) {
        // 这里单纯就是元素个数为0后,赋值一个空实例的共享空数组实例                                              
        this.elementData = EMPTY_ELEMENTDATA;                                            
    } else {                                                                             
        throw new IllegalArgumentException("Illegal Capacity: "+                         
                                           initialCapacity);                             
    }                                                                                    
}        

public ArrayList(Collection<? extends E> c) { 
    // c.toArray()底层实际调用的也是Arrays.copyOf(),根据集合c的长度创建一个Object[]新数组,把数据拷贝到新数组上                                                           
    elementData = c.toArray();                                                                                
    if ((size = elementData.length) != 0) {
        // 重复调用Arrays.copyOf()是为了防止c.toArray()不返回Object[]                                                                   
        // defend against c.toArray (incorrectly) not returning Object[]                                      
        // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)                                        
        if (elementData.getClass() != Object[].class)                                                         
            elementData = Arrays.copyOf(elementData, size, Object[].class);                                   
    } else {                                                                                                  
        // replace with empty array. 
        // 这里单纯就是元素个数为0后,赋值一个空实例的共享空数组实例                                                                            
        this.elementData = EMPTY_ELEMENTDATA;                                                                 
    }                                                                                                         
}                                                                                                                                                                                                                                         

2.4、核心方法

2.4.1、add方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 将指定的元素附加到此列表的末尾
public boolean add(E e) {  
    // 此列表在结构上被修改的次数
    modCount++;               
    add(e, elementData, size);
    return true;              
} 

private void add(E e, Object[] elementData, int s) {
    // 数组容量满了,执行扩容机制  
    if (s == elementData.length)   
        // 得到一个扩容后的新数组                 
        elementData = grow();   
    // size位置插入元素                    
    elementData[s] = e;   
    // 更新size大小:原元素个数大小+1                                 
    size = s + 1;                                   
} 

// 在此列表中的指定位置插入指定元素
public void add(int index, E element) { 
    // 校验index的范围                      
    rangeCheckForAdd(index);                                  
    modCount++; 
    // 当前元素个数大小                                              
    final int s;                                              
    Object[] elementData; 
    // 数组容量满了,执行扩容机制                                    
    if ((s = size) == (elementData = this.elementData).length)
        // 得到一个扩容后的新数组
        elementData = grow(); 
    // 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)。                                
    System.arraycopy(elementData, index,                      
                     elementData, index + 1,                  
                     s - index);    
    // 移动后,index位置空余出来,即可将element插入到index位置                          
    elementData[index] = element;      
    // 更新size大小:原元素个数大小+1                       
    size = s + 1;                                             
}  

// 将指定集合中的所有元素追加到此列表的末尾
public boolean addAll(Collection<? extends E> c) {    
    // c.toArray()底层实际调用的也是Arrays.copyOf(),根据集合c的长度创建一个Object[]新数组,把数据拷贝到新数组上              
    Object[] a = c.toArray();                                          
    modCount++;                                                        
    int numNew = a.length;                                             
    if (numNew == 0)                                                   
        return false;                                                  
    Object[] elementData;                                              
    final int s; 
    // 原数组的剩余容量不够插入数组的大小时,就进行扩容                                                      
    if (numNew > (elementData = this.elementData).length - (s = size)) 
        // 得到一个扩容后的新数组
        elementData = grow(s + numNew); 
    // 将插入数组的元素拷贝到扩容数组上                               
    System.arraycopy(a, 0, elementData, s, numNew);       
    // 更新size大小:原元素个数大小+插入数组的长度          
    size = s + numNew;                                                 
    return true;                                                       
}                                                                        

关于add方法,如果是末尾插入,那么平均时间复杂度为O(1);如果是非末尾插入,因为需要移动元素,那么平均时间复杂度为O(n)。因此,我们应该尽量避免在大数据量中调用add带索引参数的方法。

2.4.2、grow方法(扩容机制)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private Object[] grow() {              
    return grow(size + 1);         
}  

// 增加容量以确保它至少可以容纳最小容量参数指定的元素数
private Object[] grow(int minCapacity) {    //  minCapacity = size + 1 
    // 先计算新容量大小,再根据新容量大小创建一个Object[]新数组,把数据拷贝到新数组上                           
    return elementData = Arrays.copyOf(elementData,              
                                       newCapacity(minCapacity));
}     

// 返回至少与给定最小容量一样大的容量。如果足够,返回增加 50% 的当前容量。除非给定的最小容量大于 MAX_ARRAY_SIZE,否则不会返回大于 MAX_ARRAY_SIZE 的容量。
private int newCapacity(int minCapacity) {                    
    // overflow-conscious code             
    // 旧容量 = 原数组的长度                  
    int oldCapacity = elementData.length; 
    // 新容量 = 旧容量 + 旧容量右移一位(相当于除于2)                    
    int newCapacity = oldCapacity + (oldCapacity >> 1);       
    if (newCapacity - minCapacity <= 0) {  
        // 如果是调用了无参数构造方法,没有指定初始容量时                   
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
            // 默认初始容量就为10
            return Math.max(DEFAULT_CAPACITY, minCapacity);   
        if (minCapacity < 0) // overflow                      
            throw new OutOfMemoryError(); 
        // 其它情况一律size + 1                   
        return minCapacity;                                   
    }  
    // 如果还没达到最大容量,那就是新容量大小,否则返回一个返回一个巨大的容量                                                
    return (newCapacity - MAX_ARRAY_SIZE <= 0)                
        ? newCapacity                                         
        : hugeCapacity(minCapacity);                          
}   

private static int hugeCapacity(int minCapacity) {  
    if (minCapacity < 0) // overflow                
        throw new OutOfMemoryError();   
    //  size+1 > MAX_ARRAY_SIZE时,那么返回一个巨大的容量Integer.MAX_VALUE,否则MAX_ARRAY_SIZE       
    return (minCapacity > MAX_ARRAY_SIZE)           
        ? Integer.MAX_VALUE                         
        : MAX_ARRAY_SIZE;                           
}                                                                                                                                                                                                            

2.4.3、remove方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 移除此列表中指定位置的元素
public E remove(int index) {
    // 校验index的范围                                     
    Objects.checkIndex(index, size);                           
    final Object[] es = elementData;                           
    // 取出要移除的元素                                                           
    @SuppressWarnings("unchecked") E oldValue = (E) es[index]; 
    // 删除元素
    fastRemove(es, index);                                     

    // 返回要移除的元素       
    return oldValue;                                           
} 

// 跳过边界检查并且不返回删除的值的私有删除方法
private void fastRemove(Object[] es, int i) {                           
    modCount++;                                                         
    final int newSize;
    // 如果删除的元素索引位置非最后一个元素位置,那么将当前位于该位置的元素(如果有)和任何后续元素向左移动(将其索引减一)。                                                    
    if ((newSize = size - 1) > i)                                       
        System.arraycopy(es, i + 1, es, i, newSize - i); 
    // 移动原size-1位置多余了,需要置null               
    es[size = newSize] = null;                                          
}

// 从此列表中移除第一次出现的指定元素(如果存在)。如果列表不包含该元素,则它不变。
public boolean remove(Object o) {    
    final Object[] es = elementData; 
    final int size = this.size;      
    int i = 0;  
    // 使用了java label语法                     
    found: { 
        // 如果传入null,那么遍历去查看是否存在null的元素,找到就跳出循环                   
        if (o == null) {             
            for (; i < size; i++)    
                if (es[i] == null)   
                    break found;     
        } else { 
            // 如果传入不为null,那么遍历去查看是否存在相同的元素,找到就跳出循环                        
            for (; i < size; i++)    
                if (o.equals(es[i])) 
                    break found;     
        }  
        // 上面两个条件下都找不到要查找的元素,则返回false                          
        return false;                
    }  
    // 找到要删除的元素后执行删除                              
    fastRemove(es, i);               
    return true;                     
}                                                                                                                                                                        

2.4.4、set方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 用指定元素替换此列表中指定位置的元素
public E set(int index, E element) {
    // 校验index的范围           
    Objects.checkIndex(index, size);
    // 获取索引位置对应的元素
    E oldValue = elementData(index);
    // 设置索引位置对应的元素
    elementData[index] = element;
    // 返回要修改的元素   
    return oldValue;                
}    

// 返回索引位置对应的元素
E elementData(int index) {        
    return (E) elementData[index];
}                                                                

2.4.5、get方法

1
2
3
4
5
6
7
// 返回此列表中指定位置的元素
public E get(int index) {        
    // 校验index的范围    
    Objects.checkIndex(index, size); 
    // 返回索引位置对应的元素
    return elementData(index);       
}                                    

2.5、其它方法

2.5.1、ensureCapacity方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 如有必要,增加此ArrayList实例的容量,以确保它至少可以容纳最小容量参数指定的元素数。
// 也就是说,这个方法的使用场景是如果已经预知ArrayList需要比较大的容量,调用这个方法可以减少ArrayList内部分配和扩展的次数
public void ensureCapacity(int minCapacity) {                 
    // 最小容量不得低于原数组的长度,同时原数组不是第一次调用空参数构造方法和第一次扩容
    if (minCapacity > elementData.length                      
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
             && minCapacity <= DEFAULT_CAPACITY)) {           
        modCount++;       
        // 进行扩容                                    
        grow(minCapacity);                                    
    }                                                         
}                                                             

2.5.2、trimToSize方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 将此ArrayList实例的容量修剪为列表的当前大小。应用程序可以使用此操作来最小化ArrayList实例的存储
public void trimToSize() {                   
    modCount++;   
    // 数组容量没装满元素                        
    if (size < elementData.length) {
        // 如果元素个数为0,那么赋值一个空实例的共享空数组实例         
        elementData = (size == 0)            
          ? EMPTY_ELEMENTDATA  
          // 否则拷贝元素到新数组上,新数组的大小就是元素个数的数量              
          : Arrays.copyOf(elementData, size);
    }                                        
}                                            

三、总结

ArrayList的源码中,并没有与并发相关的代码,所以说ArrayList是非线程安全类,在并发环境下多个线程同时操作,会引发不可预知的错误。应该使用Collections.synchronizedList方法包装列表,最好在创建时完成,如下代码:

1
List list = Collections.synchronizedList(new ArrayList(...));

另外,ArrayList是经常用到的一个容器,要根据实际的业务场景来使用,比如说,当添加、删除数据不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能。