ArrayList内部原理解析

注:本文解析的 ArrayList 源代码基于 Java 1.8 。

Header

之前讲了 HashMap 的原理后,今天来看一下 ArrayList 。

ArrayList 也是非常常用的集合类。它是有序的并且可以存储重复元素的。 ArrayList 底层其实就是一个数组,并且会动态扩容的。

源码分析

构造方法

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

public ArrayList() {
    // 默认为空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

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()

public boolean add(E e) {
    // 确保数组的容量,保证可以添加该元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将该元素放入数组中
    elementData[size++] = e;
    return true;
}

发现在 add() 方法中,代码很简短。可以看出之前的预操作都放入了 ensureCapacityInternal 方法中,这个方法会去确保该元素在数组中有位置可以放入。

那么我们来看看这个方法:

private void ensureCapacityInternal(int minCapacity) {
    // 如果数组是空的,那么会初始化该数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // DEFAULT_CAPACITY 为 10 ,所以调用无参默认 ArrayList 构造方法初始化的话,默认的数组容量为 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 确保数组的容量,如果不够的话,调用 grow 方法扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

看了半天,扩容是在 grow 方法中完成的,所以我们接着跟进。

private void grow(int minCapacity) {
    // 当前数组的容量
    int oldCapacity = elementData.length;
    // 新数组扩容为原来容量的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新数组扩容容量还是比最少需要的容量还要小的话,就设置扩充容量为最小需要的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //判断新数组容量是否已经超出最大数组范围,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
    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);
}

扩容方法其实就是新创建一个数组,然后将旧数组的元素都复制到新数组里面。

当然,add 还有一个重载的方法 add(int index, E element) ,顺便我们也来看一下。

public void add(int index, E element) {
    // 判断 index 有没有超出索引的范围
    rangeCheckForAdd(index);
    // 和之前的操作是一样的,都是保证数组的容量足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将指定位置及其后面数据向后移动一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 将该元素添加到指定的数组位置
    elementData[index] = element;
    // ArrayList 的大小改变
    size++;
}

好了,add 方法看的差不多了,剩下还有一个 addAll(Collection<? extends E> c) 方法也是换汤不换药的,可以自己回去看下,这里就不讲了。

get()

get 方法很简单,就是在数组中返回指定位置的元素即可。

public E get(int index) {
    // 检查 index 有没有超出索引的范围
    rangeCheck(index);
    // 返回指定位置的元素
    return elementData(index);
}

remove()

public E remove(int index) {
    // 检查 index 有没有超出索引的范围
    rangeCheck(index);

    modCount++;
    // 保存一下需要删除的数据
    E oldValue = elementData(index);
    // 让指定删除的位置后面的数据,向前移动一位
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 方便 gc 释放内存
    elementData[--size] = null; // clear to let GC do its work
    // 返回旧值
    return oldValue;
}

remove 中主要是将之后的元素都向前一位移动,然后将最后一位的值设置为空。最后,返回已经删除的值。

同样,remove 还有一个重载的方法 remove(Object o)

public boolean remove(Object o) {
    if (o == null) {
        // 如果有元素的值为 null 的话,移除该元素,fastRemove 的操作和上面的 remove(int index) 是类似的
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 如果有元素的值等于 o 的话,移除该元素
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

clear()

clear 方法无非就是遍历数组,然后把所有的值都置为 null 。

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

Footer

至此,ArrayList 主要的几个方法就讲完了。ArrayList 的源码还是比较简单的,基本上都可以看得明白。

我们来总结一下:

  1. ArrayList底层是基于数组来实现的,因此在 get 的时候效率高,而 add 或者 remove 的时候,效率低;
  2. 调用默认的 ArrayList 无参构造方法的话,数组的初始容量为 10 ;
  3. ArrayList 会自动扩容,扩容的时候,会将容量扩至原来的 1.5 倍;
  4. ArrayList 不是线程安全的;

那么今天就这样了,之后有空给大家讲讲 LinkedList 。

    原文作者:HashMap源码分析
    原文地址: https://juejin.im/entry/5a6db5a3518825732739dabd
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞