Java集合之List——ArrayList详解

Java集合之List——ArrayList详解

简介

  1. ArrayList是List接口的大小可变数组的实现类(容量可自动增长);
  2. ArrayList非同步的;
  3. ArrayListiteratorlistIterator方法返回的迭代器是fail-fast的。

源码解读

继承关系

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

结构图
在这里插入图片描述
先简单介绍一下RandomAccessCloneableSerializable三个接口:

  • RandomAccess:与使用效率相关,表明ArrayList支持快速(通常是固定时间)随机访问;
  • Cloneable:实现了该接口,就可以使用Object.clone方法,就是ArrayList的拷贝,返回类型为Object,注意写法的问题,测试代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Demo01 {
    public static void main(String[] args) {
    Integer[] arr = new Integer[]{1, 44, 3, 4, 5, 6, 7, 8, 9};
    //正确写法
    ArrayList<Integer> list = new ArrayList<>();
    Collections.addAll(list, arr);
    Object list2 = list.clone();
    /*错误写法
    List<Integer> list = new ArrayList<>();
    Collections.addAll(list, arr);
    Object list2 = list.clone();//报错,List中找不到clone方法
    */
    }
    }
  • Serializable:表明该类具有序列化功能,该类可以被序列化。

再介绍一下AbstractList

  • 通过看源码发现这是一个抽象类,发现他又继承了一个抽象类AbstractCollection和实现了接口List(由上方结构图可以清晰看出)。
  • 通过查阅资料,这么安排继承关系是因为,可以从以该类为父类的子类中抽取一些实现相同的方法,将其实现为一个抽象类,以达到代码复用代码简洁的目的。
  • 还有一个疑问是为什么抽象类AbstractCollection已经实现了接口ListArrayList还要实现List接口呢?具体原因可见
  • 这里简单描述一下:开发collection 的作者Josh说这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。

    成员变量

    仅介绍部分。
    集合中所有元素均存储在数组中,类中的定义如下:
    1
    transient Object[] elementData; // non-private to simplify nested class access
    首先数组的权限设定为了Default,是为了方便内部类地使用;
    其次用了关键字transient进行修饰,它表示该变量将不被序列化处理。具体可参考
1
private int size;//代表集合当前的大小

构造方法

  1. public ArrayList();,构造一个初始容量为10的空列表
  2. public ArrayList(int initialCapacity);,构造一个容量为initialCapacity(给定参数)的空列表
  3. public ArrayList(Collection<? extends E> c);,构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回
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
/**
* Constructs an empty list with the specified initial capacity.
* 构造一个具有给定容量大小的空列表
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}

/**
* Constructs an empty list with an initial capacity of ten.
* 构造一个容量为10的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* 即构造一个给定容器拷贝,元素顺序不变
* @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;
}
}

扩容机制

以下以add方法为例

1
2
3
4
5
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 保证容器大小够用
elementData[size++] = e;
return true;
}

  1. 通过ensureCapacityInternal方法来保证内部容量足够容纳新添加的元素(可能是多个元素,如addAll方法)
    1
    2
    3
    4
    private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); //calculateCapacity返回添加元素后容器的最小容量
    //ensureExplicitCapacity先判断容器是否需要扩容,若需要扩容则调用扩容函数grow
    }
  2. 然后通过calculateCapacity方法来确定添加元素后容器的最小容量
    1
    2
    3
    4
    5
    6
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }
  3. 再通过ensureExplicitCapacity方法判断容器是否需要扩容
    1
    2
    3
    4
    5
    6
    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }
  4. 若需要扩容紧接着调用扩容方法grow
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容后大小为原大小的1.5倍
    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;
    }
    总结:ArrayList在每次插入新元素时,总是先保证容量够用,然后再插入新元素,而每次扩容为原容量的1.5倍
    关于为什么是1.5倍:如果扩容太多会导致空间的浪费,而扩容太少会导致频繁进行扩容,而1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

    常用方法

    添加元素

    1
    2
    3
    4
    boolean add(E e) //将指定的元素追加到此列表的末尾。  
    void add(int index, E element) //在此列表中的指定位置插入指定的元素。
    boolean addAll(Collection<? extends E> c) //按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
    boolean addAll(int index, Collection<? extends E> c) //将指定集合中的所有元素插入到此列表中,从指定的位置开始。

    删除元素

    1
    2
    3
    4
    5
    6
    E remove(int index) //删除该列表中指定位置的元素。  
    boolean remove(Object o) //从列表中删除指定元素的第一个出现(如果存在)。
    boolean removeAll(Collection<?> c) //从此列表中删除指定集合中包含的所有元素,即集合取差集 。
    boolean removeIf(Predicate<? super E> filter) //删除满足给定谓词的此集合的所有元素,利用正则表达式筛选出取出符合条件的。
    protected void removeRange(int fromIndex, int toIndex) //从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
    boolean retainAll(Collection<?> c) //仅保留此列表中包含在指定集合中的元素,集合取交集。

    修改元素

    1
    2
    E set(int index, E element) //用指定的元素替换此列表中指定位置的元素。 
    void replaceAll(UnaryOperator<E> operator) //将该列表的每个元素替换为将该运算符应用于该元素的结果。

    获取元素

    1
    2
    3
    E get(int index) //返回此列表中指定位置的元素。
    int indexOf(Object o) //返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
    int lastIndexOf(Object o) //返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。

    其它方法

    1
    2
    3
    4
    5
    void clear() //从列表中删除所有元素。  
    boolean contains(Object o) //如果此列表包含指定的元素,则返回 true 。
    boolean isEmpty() //如果此列表不包含元素,则返回 true 。
    int size() //返回此列表中的元素数。
    List<E> subList(int fromIndex, int toIndex) //返回此列表中指定的 fromIndex (包括)和 toIndex之间的独占视图。

    效率分析

    arrayList由于本质是数组,所以它在数据的查询方面会很快;而在插入删除元素过程时,如果操作在数组末尾,时间复杂度为O(1),而在操作在指定位置,时间复杂度为O(n),性能会下降很多,因为需要挪动元素

综上ArrayList,在查询多,增删(在List前段位置)操作少时比较合适。