jdk1.8-Java集合框架之--ArrayList源码分析

1、说到ArrayList,你们都知道是有序集合,有下标,读速度快,写速度慢。可是问一句为何是有序、为何有下标、为何读速度快、为何什么写速度慢,估计不少人答不上来。那么接下来咱们就从源码的角度来分析。

2、咱们先来看一张图:

经过这张图,咱们能够看到,ArrayList类,继承了一个抽象类AbstractList,实现了四个接口,分别是Cloneable、Serializable、RandomAccess、List这四个接口。Cloneable、Serializable这两个接口咱们知道是对象克隆和序列化相关的,咱们暂时无论。

  1. 咱们先看一下RandomAccess接口。
public interface RandomAccess {
}
复制代码

打开源码一看,这是一个空接口,即标记接口,这个接口的做用就是使ArrayList具有快速随机访问的功能。什么是随机访问?就是随机访问ArrayList中的任何一个元素。那ArrayList是如何快速随机访问任何一个元素的呢,咱们稍后再说明。数组

  1. 咱们再来看下父类AbstractList,从上面的图1能够看出,AbstractList是一个抽象类,也一样实现了List接口,咱们能够猜测,AbstractList是对List的具体实现类进行的抽象化的提取。因为篇幅缘由,代码为就不贴了。bash

  2. 接下来咱们就看ArrayList类代码的具体实现,首先咱们看到ArrayList类定义了4个常量,2个实例变量,分别是:框架

//默认容量
    private static final int DEFAULT_CAPACITY = 10;
    //空元素数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默认容量对应的空元素数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //元素,被transient关键字修饰,无需序列化
    transient Object[] elementData; // non-private to simplify nested class access
    //长度
    private int size;
    //最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
复制代码
从以上源码咱们能够猜测,ArrayList是经过内部维护一个的Object对象数组实现的,且默认的ArrayList容量是10。
复制代码
  1. 接下来看下ArrayList的构造函数,ArrayList一共定义了3个构造函数,分别是:
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //指定初始化容量,若是指定容量大于0,则设置元素数组为指定长度的对象数组;
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //若是指定容量等于0,那么设置元素数组为空数组;
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //若是指定容易小于0,则抛出异常;
            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) {
            // 若是元素数组长度不为0,且数组对象不是Object,则将数组元素转为Object对象;
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 若是元素数组长度为空,则替换成常量空数组;
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
复制代码
  1. 接下来咱们具体分析下ArrayList的几个经常使用方法,首先是get:
public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    E elementData(int index) {
        return (E) elementData[index];
    }
复制代码

很明显,咱们能看得出,get方法纯粹就是ArrayList内部维护的元素数组操做,经过数组下标取值,很是简单。至此,回顾一开始的问题,ArrayList为何读取快?由于是数组下标直接读取操做,复杂度是O(1)。dom

  1. 继续看无参数add方法:
public boolean add(E e) { 
        //将数组长度+1做为add操做须要的最小容量传入方法中
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;    //将元素添加到数组末端,所以主要逻辑在前一句代码
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //根据当前元素数组长度,和add操做须要的最小容量作比较,肯定操做须要的最小容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //该步骤确保元素数组知足操做所需的最小容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若所需的最小容量大于当前元素数组长度,则对元素数组进行扩容处理
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //扩容操做
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 新容量=旧容量+旧容量/2;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //若第一步扩容后,新容量仍是不知足所需最小容量,则将新容量设置为所需最小容量;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 若新容量比最大的容许容量还大,那么须要从新计算新容量;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 计算完毕,将元素复制到新容量的数组上
        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;
    }
复制代码

以上无参数的add方法源码,其实带参数的add方法跟无参数的基本同样,只是增长了一个指定位置到数组末端元素复制的方法,以下:函数

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++;
    }
复制代码

能看得出,ArrayList的无参数add方法,须要通过两个大步骤,第一步是计算容量,第二步是若是扩容了,须要将原来数组内的元素,全量复制到新的数组上。至此,咱们又能回答一开始的问题,ArrayList为何写操做慢?由于ArrayList写操做会对部分甚至所有元素进行移动操做,同理,对于set、remove等写操做的方法,总体上都是这样的操做,因此写操做效率低。ui

第一篇Java集合框架之ArrayList就简单讲到这,下节讲LinkedList,有不一样意见或建议欢迎指出,谢谢。

相关文章
相关标签/搜索