ArrayList源码分析

ArrayList

基于JDK1.8进行分析

简介

实现List接口的一个可变数组,元素内容可以为空。除了实现List接口外,还提供了一些方法来操纵内部用于存储列表数组的大小。与Vector大致相等,但不是同步的。

类图

ArrayList类图
从上图可以得知:

  1. 集成AbstractList类,实现了List接口。是一个动态数组,提供了增、删、改和遍历的功能。
  2. 实现RandomAccess接口提供了随机访问的功能。
  3. 实现Cloneable接口可以进行克隆。
  4. 实现Serializable接口可以进行序列化,进行序列化传输。

源码分析

前提:

System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制。其函数原型是:

/**
* @param      src      the source array. 源数组
* @param      srcPos   starting position in the source array. 源数组的起始位置
* @param      dest     the destination array. 目标数组
* @param      destPos  starting position in the destination data. 目标数组的起始位置
* @param      length   the number of array elements to be copied. 复制的长度
*/
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
  • 属性

    /**
         * Default initial capacity.
         * 默认的初始容量
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * Shared empty array instance used for empty instances.
         * 空实例的共享空数组对象实例
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        /**
         * Shared empty array instance used for default sized empty instances. We
         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
         * first element is added.
         * 共享的数组对象被用于默认大小空对象的实例。区分于EMPTY_ELEMENTDATA,知道需要扩容多少 ,当添加元
         * 素的时候
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         * 
         * 存储ArrayList元素的数组缓冲区,ArrayList的大小就是数组缓冲区的容量。
         * 当任何空的ArrayList满足elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA条件,在添加初始元素时,会
         * 设置默认的扩容属性
         * 
         */
        transient Object[] elementData; // non-private to simplify nested class access
    
        /**
         * The size of the ArrayList (the number of elements it contains).
         * ArrayList的大小
         *
         * @serial
         */
        private int size;
    
  • 构造方法

     /**
         * 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) { //初始容量大于0,实例一个新对象数组
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) { //初始容量等于0,返回一个空对象数组
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                //初始容量小于0,提示异常,参数不合法
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
        /**
         * Constructs an empty list with an initial capacity of ten.
         * 使用默认构造容量,构建ArrayList对象,并且在添加第一个元素时,就会设置扩容量为默认的扩容量
         * 
         */
        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)
                //转换的数组对象是不是Object类型的
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);//将集合元素转成Object类型
            } else {
                 //如果集合中不存在元素
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    
  • 方法

    add(e)

    public boolean add(E e) {
            // 确认容量
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            // 直接将元素添加在数组中
            elementData[size++] = e;
            return true;
    }
    
     private void ensureCapacityInternal(int minCapacity) {
        // 进一步确认ArrayList的容量,看是否需要进行扩容
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
       // 如果elementData为空,则返回默认容量和minCapacity中的最大值
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 否则直接返回minCapacity
        return minCapacity;
    }
    
     private void ensureExplicitCapacity(int minCapacity) {
            // 修改次数自增
            modCount++;
    
            // overflow-conscious code
            // 判断是否需要扩容,双重校验,防止数组下表越界
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
     }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        // 原容量
        int oldCapacity = elementData.length;
        // 扩容,相当于扩大为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 确认最终容量
        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);
    }
    

    分析:触发扩容的条件,如果添加的元素比当前的数组多,就会触发扩容。因此在循环中使用时,避免重复扩容造成OOM异常。

    建议:初始化的时候就设置好扩容量,可以避免OOM异常。

    add(int index, E element)

    public void add(int index, E element) {
            // 越界检查
            rangeCheckForAdd(index);
    
            // 确认容量
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            // 将index及其之后的元素往后移动一位,将index位置空出来
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            // 在index插入元素
            elementData[index] = element;
            // 元素个数自增
            size++;
        }
    

    get(index):

    public E get(int index) {
        // 越界检查
        rangeCheck(index);
        // 获取对应位置上的数据
        return elementData(index);
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    

    remove(index):

    public E remove(int index) {
        // 越界检查
        rangeCheck(index);
    
        // 修改次数自增
        modCount++;
        // 获取对应index上的元素
        E oldValue = elementData(index);
    
        // 判断index是否在最后一个位置
        int numMoved = size - index - 1;
        // 如果不是,则需要将index之后的元素往前移动一位
        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(o):

    public boolean remove(Object o) {
        // 如果被移除元素为null
        if (o == null) {
            // 循环遍历
            for (int index = 0; index < size; index++)
                // 注意这里判断null是用的“==”
                if (elementData[index] == null) {
                    // 快速remove元素
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                // 这里判断相等是用的equals方法,注意和上面对比
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    
    private void fastRemove(int index) {
        // 注意这里并未做越界检查,毕竟叫fastRemove
        // 修改次数自增
        modCount++;
        // 判断是否是最后一个元素,这里的操作和remove(index)是一样的
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
    

    addAll©:

    public boolean addAll(Collection<? extends E> c) {
            Object[] a = c.toArray();
            int numNew = a.length;
            //确认扩容的大小,并进行数组的扩容
            ensureCapacityInternal(size + numNew);  // Increments modCount
            //将元素追加到最后
            System.arraycopy(a, 0, elementData, size, numNew);
            //赋值最新的元素长度
            size += numNew;
            return numNew != 0;
        }
    

    分析:addAll 求并集,和add(e) 方法类似。

    retainAll©:

    public boolean retainAll(Collection<?> c) {
        // 判空
        Objects.requireNonNull(c);
        // 批量remove complement为true表示保存包含在c集合的元素,这样就求出交集了
        return batchRemove(c, true);
    }
    
    private boolean batchRemove(Collection<?> c, boolean complement) {
            final Object[] elementData = this.elementData;
            // 读写指针 读指针遍历,写指针只有在条件符合时才自增,这样不需要额外的空间
            int r = 0, w = 0;
            boolean modified = false;
            try {
                // 遍历
                for (; r < size; r++)
                    // 如果c集合中包含遍历元素,则把元素放入写指针位置(以complement为准)
                    if (c.contains(elementData[r]) == complement)
                        elementData[w++] = elementData[r];
            } finally {
                // Preserve behavioral compatibility with AbstractCollection,
                // even if c.contains() throws.
                // 正常情况下,r与size是相等的,这里是对异常的判断
                if (r != size) {
                    // 将未读的元素拷贝到写指针后面
                    System.arraycopy(elementData, r,
                                     elementData, w,
                                     size - r);
                    w += size - r;
                }
                // 将写指针后的元素全部置空
                if (w != size) {
                    // clear to let GC do its work
                    for (int i = w; i < size; i++)
                        elementData[i] = null;
                    modCount += size - w;
                    size = w;
                    modified = true;
                }
            }
            return modified;
        }
    

    分析:retainAll 求交集,使用读写指针进行操作,不占用额外空间;并且使用finally 处理异常和置空情况。

    removeAll©:

    public boolean removeAll(Collection<?> c) {
            Objects.requireNonNull(c);
            return batchRemove(c, false); //complement=false,  保留不在c容器种的元素
        }
    

    分析:removeAll求差集,是单向差集,返回集合 c 中没有的元素。

总结

  1. ArrayList内部使用数组存储元素(连续的内存空间),默认容量是10,并且支持存储null值;
  2. ArrayList添加的元素长度大于当前长度时,会进行1.5倍的扩容(不会缩容);
  3. ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
  4. ArrayList在尾部添加/删除元素极快,时间复杂度为O(1);
  5. ArrayList在中间添加/删除元素比较慢,因为要进行元素的移动,时间复杂度为O(n);
  6. ArrayList支持并集,交集和单向差集。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值