ArrayList源码阅读:无参构造方法、添加元素以及扩容机制(一)

这会是一个系列的文章,这是开篇文,因为个人时间有限和文字表达能力欠佳,如有问题请留言评论。


ArrayList源码分析思路:

1、ArrayList概述

2、ArrayList的构造函数,也就是我们常见一个ArrayList的方法

3、ArrayList的添加元素的方法,以及扩容机制

4、ArrayList删除元素的常用方法

ArrayList概述

ArrayList基本特点:

  • ArrayList底层是一个可动态扩容的数组

  • ArrayList允许存放(不止一个)null元素

  • 允许存放重复数据,存储时按照元素的添加顺序存储

  • ArrayList不是一个线程安全的集合,如果集合的增删操作需要保证线程的安全性,可以考虑使用CopyOWriteArrayList或者使用collections.synchronizedList(Lise l)函数返回一个线程安全的ArrayList类。

ArrayList的继承关系

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

从ArrayList的继承关系来看,它继承自AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable接口。

  • 其中AbstractList和List接口中规定了ArrayList作为一个集合必须具备的一些属性和方法,ArrayList本身覆写了基类和接口中的大部分方法

  • ArrayList实现RandomAccess接口表示其支持随机快速访问,查看RandomAccess的源码看到它并没有抽象方法,这个接口只是一个标识,用来标识实现这个接口的类具有随机快速访问的能力,对于ArrayList来说,通过get(index i)方法来访问元素可以达到O(1)的时间复杂度。有些集合类就不具备这种快速随机访问的能力,比如LinkedList,它就没有实现这个接口

  • ArrayList实现Cloneable接口标识着它可以被克隆/复制,其内部实现了clone方法供使用者调用来对ArrayList进行克隆,但其实实际是通过Arrays.copyOf完成了对Arraylist进行了浅复制,也就是通过改变clone后的集合中的元素,这样源集合中的元素也会跟着改变

  • 实现java.io.Serializable标识着集合可以被序列化

ArrayList的构造方法

先来看一下与构造参数有关的几个全局变量,我对英文描述进行了简单解释

     /**
         * 默认的数组容量
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;

        /**
         * 共享的空数组实例
         * Shared empty array instance used for empty instances.
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};

        /**
         * 共享的空数组实例,当第一次add元素时使用它来判断数组大小是否设置为DEFAULT_CAPACITY
         * 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.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

        /**
         * 真正装载集合元素的底层数组,被transient关键字修饰表示该成员变量无法被Serializable序列化
         * 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.
         */
        transient Object[] elementData;

无参构造方法:

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

​这个是我使用最多的构造方法,其内部实现是将真正用来存放集合元素的数组指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组。这里和源码的注释有点不一样,源码中直接说明了这会创建一个容量为10的空列表,但是这里却没有体现出容量为10的语句,其实这并不是源码说错了,而是在第一次执行添加元素操作的时候才会将数组的的容量指定为10,我觉得这是源码说明的不够严谨。

添加元素和扩容机制

既然上面已经创建了一个空集合,那现在就添加一个元素,看一下ArrayList内部是如何添加这个元素并指定数组的长度为10,并且是如何扩容的。

第一步:
    /**
    * 在列表末尾添加指定元素
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 这是操作数,目前还不知道它的作用,暂且不管,不影响下面的理解
        modCount++;
        // e代表添加的元素,elementData代表的是内部存放元素的数组,size代表集合的大小,因为是成员变量,默认值是0
        add(e, elementData, size);
        return true;
    }
第二步:
    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();// 方法返回的结果是将数组的容量设置为10-
        elementData[s] = e;// s = 0,在索引为0的位置添加元素-
        size = s + 1;// 将集合的长度设置为1-
    }
第三步:
    private Object[] grow() {
            return grow(size + 1);
    }
第四步:
    private Object[] grow(int minCapacity) { // minCapacity = 0 + 1 = 1
            return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
    }// 这里面调用的Arrays中的copyOf方法,先暂时不考虑这个方法的具体实现,知道它的功能就行了,查看API可知:复制指定的数组,以便复制具有指定的长度。 
第五步:看newCapacity(minCapacity)方法:
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        // 初始数组为空,所以oldCapacity = 0
        int oldCapacity = elementData.length;
        // oldCapacity >> 1等价于oldCapacity/2,因此newCapacity = 0
        // 这也代表着每次扩容时数组的容量都会变为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 条件结果为true
        if (newCapacity - minCapacity <= 0) {
            // 无参构造的方法中将真实存放元素的数组指向了一个空数组,所以条件结果为true
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                // Max.max(10, 1);返回最大值10,到这里就明白了为什么源码注释说明使用无参构造函数会创建一个容量为10的空列表
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        // 如果上面的都不满足则会返回扩容后的数组长度,用到了三元运算符,很简单就不再解释了
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

    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集合,并添加了一个元素,通过源码了解了添加元素的过程以及扩容机制。


开发工具:IntelliJ IDEA
JDK版本:1.9
API文档查看工具:Velocity

猜你喜欢

转载自www.cnblogs.com/guokaiyue7112/p/9179210.html