JAVA集合源码解析 ArrayList 探索(基于JDK1.8)

JDK1.8ArrayList探索

本文基于JDK1.8进行分析,由于篇幅有限,只着重分析核心方法。

先来个大纲方便各位客官快速获取所需:

1.简介

ArrayList 其实是一个动态数组,数组作为其底层支撑,内部的数组实例是 Object[ ] 型的,所以允许所有元素,包括空值。ArrayList 是不同步的,建议在单线程环境中使用。

2.探索

2.1类关系

ArrayList

  1. ArrayList 继承了 AbstractList 类,能够重写所有列表操作。
  2. ArrayList 实现了 List 接口,实现所有可选列表操作。
  3. ArraList 实现了实现了Cloneable 接口,能够使用clone()方法。
  4. ArraList实现了 Serializable 接口,支持序列化操作。

2.2属性

    /**
     * 默认的初始化容量
     */
    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; // non-private to simplify nested class access

    /**
     * 包含元素的大小
     *
     * @serial
     */
    private int size;
  1. 默认的初始化容量为 10.
  2. elementData为数组缓冲区。

2.3构造方法

  • ArrayList(int initialCapacity)
    /**
     * 带有初始化容量的构造函数
     *
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//如果该容量大于0
            this.elementData = new Object[initialCapacity];//用该容量初始化数组
        } else if (initialCapacity == 0) {//如果为0
            this.elementData = EMPTY_ELEMENTDATA;//初始化空的数组
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

如果传入容量不为0,就用该容量进行初始化数组,否则初始化为空数组。

  • ArrayList()
    /**
     * 默认构造函数
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//构造空的数组缓冲区
    }

构造空的数组。

  • ArrayList(Collection
    /**
     * 包含指定集合的构造函数
     *
     */
    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)//是否为Object数组
                elementData = Arrays.copyOf(elementData, size, Object[].class);//继续复制不为Object的数组
        } else {
            // 设置元素数组为空
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

先将集合转换为数组类型,然后保存到 elementData

2.4核心方法

  • add(E e)
    /**
     * 添加元素
     *
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 增加modCount!
        elementData[size++] = e;//添加到数组缓冲区中
        return true;
    }

调用ensureCapacityInternal方法

private void ensureCapacityInternal(int minCapacity) { 
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

继续调用ensureExplicitCapacity()和calculateCapacity()方法

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++;//结构性加1

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//如果添加元素后长度大于数组缓冲区长度
            grow(minCapacity);//进行扩容操作
    }

1)结构性加1。
2)如果添加元素后长度大于数组缓冲区长度,进行扩容操作。

    /**
     * 增加容量以确保它至少能容纳最小容量参数指定的元素数量。
     *
     */
    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);//拷贝扩大容量
    }

1)新的数组容量扩大为原来的1.5倍。
2)如果超出最大数组容量,重新赋值新数组容量大小。
3)将元数组重新拷贝到容量为的newCapacity的数组中。

  • set(int index, E element)
    /**
     * 用指定的元素替换掉在列表中指定位置的值
     */
    public E set(int index, E element) {  
        rangeCheck(index);//越界检查,超出会抛出 IndexOutOfBoundsException

        E oldValue = elementData(index);//保存指定位置的旧值
        elementData[index] = element;//替换为新值
        return oldValue;//返回旧值
    }

1)检查越界否。
2)保存旧值,替换新值到指定位置。
3)返回原位置旧值。

  • get(int index)
    /**
     * 返回元素在数组中的位置
     *
     */
    public E get(int index) {   
        rangeCheck(index);//越界检查,超出会抛出 IndexOutOfBoundsException

        return elementData(index);//调用elementData方法
    }

1)先判断是否越界,然后调用elementData方法。
2)elementData方法里面直接返回该索引在数组中的值。

  • remove(int index)
    /**
     * 移除在数组中指定位置的元素
     *
     */
    public E remove(int index) { 
        rangeCheck(index);//越界检查,超出会抛出 IndexOutOfBoundsException

        modCount++;//结构性加1
        E oldValue = elementData(index);//保存指定位置值

        int numMoved = size - index - 1;//需要移动的个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//从要移除位置的后一位重新复制到数组中
        elementData[--size] = null; // 为方便进行 GC

        return oldValue;//返回旧值
    }

1)进行越界检查,结构性加1 。
2)计算需要移动的位置个数,如果大于0,就重新从移除位置后一位拷贝剩下数组元素。
3)把数组最后一个元素置空,方便进行GC。
4)返回旧值。

  • indexOf(Object o)
    /**
     * 返回此列表中第一次出现指定元素的索引
     *
     */
    public int indexOf(Object o) {  
        if (o == null) {//如果为空
            for (int i = 0; i < size; i++)//遍历数组
                if (elementData[i]==null)//找到第一个为null的位置
                    return i;
        } else {//如果不为空
            for (int i = 0; i < size; i++)//照样遍历
                if (o.equals(elementData[i]))//找到与指定值相等的序列
                    return i;//返回序列号
        }
        return -1;//如果不存在,返回-1
    }

1)指定元素为空,遍历找到空值,否则遍历找到相等值的序列。
2)在数组中没找到返回-1。

3.总结

  1. ArrayList 是一个动态数组,底层是比较简单的,因为是数组,所以进行随机读取的效率较高,但是在进行插入,删除元素时,底层方法要进行大量的 Arrays.copyOf 或者 System.arraycopy 操作,比较耗时,效率低。
  2. ArrayList 不是同步的,对列表的非同步访问可以用这种方式: List list = Collections.synchronizedList(new ArrayList(…));

JAVA之路路漫漫,吾将上下而求索,希望和大家一起交流进步!

猜你喜欢

转载自blog.csdn.net/ouzhuangzhuang/article/details/80258192