java集合类源代码分析_ArrayList

        🔶 简介

        🔶 继承结构

  目录结构  🔶 核心属性        

        🔶 核心方法

        🔶 总结

🎈 ArrayList简介

  ArrayList就是动态数组,能够灵活的设置数组大小,提供了addALL,removeAll等数组中没有的方法,允许存储重复的元素和null。

🎈 继承结构

  • 继承了AbstractList,实现了List接口;

  • 实现了RandomAccess接口,支持快速随机访问,实际上RandomAccess为空的接口,ArrayList内部维护了一个Object数组,数组本身就支持快速随机访问;
  • 实现了Serializable接口,支持序列化;

  • 实现了Cloneable接口,支持浅拷贝;

🎈 核心属性

    /**
     * 默认的数组大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 调用指定大小或传入集合的构造函数,传入的大小为0或集合大小为0时,默认初始化一个空的Object数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 调用空的构造函数时,初始化一个默认的空的Object数组
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 内部维护的一个Object数组,transient表示不可序列化
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList中包含元素的个数
     */
    private int size;

🎈 核心方法

  • 🎐 构造函数
 /**
     * 指定大小的构造函数
     *
     * @param  initialCapacity  初始化时指定ArrayList的大小
     * @throws IllegalArgumentException 如果指定大小小于0,抛出非法参数异常
     */
    public ArrayList(int initialCapacity) {
     //如果指定的大小大于0,则初始化一个该大小的Object数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //如果指定的大小为0,则初始化一个空的Object数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 空的构造函数,初始化一个空的Object数组
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 指定集合的构造函数
     *
     * @param c 集合类对象
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        //调用Collection对象的toArray方法返回该集合元素的Object数组
        elementData = c.toArray();
        //如果集合的大小不为0
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //如果Collection对象的toArray方法返回的不是Objcet类型的数组
            if (elementData.getClass() != Object[].class)
                //通过Arrays.copyof方法转换成Object数组
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果集合的大小为0,则初始化空的Object数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • 🎐 扩容方法
    /**
     *
     * 如有必要,可以通过此方法增加ArrayList实例的容量,以确保它至少能够容纳元素的数量
     * @param   minCapacity   最小需要的容量
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    /**
     *    得到扩容的最小需要的容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

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

    /**
     * 判断是否需要扩容
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果容量已经超过数组的长度,则需要扩容
        if (minCapacity - elementData.length > 0)
            //扩容操作的核心方法
            grow(minCapacity);
    }

    /**
     * ArrayList的最大容量:2147483639
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList扩容的核心方法
     * @param minCapacity 最小需要的容量
     */
    private void grow(int minCapacity) {
        // 获取扩容前数组的长度
        int oldCapacity = elementData.length;
        //计算扩容后的长度:旧容量大小 + 旧容量大小右移1位(即除2) = 1.5倍旧容量的大小
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的容量依旧不够,则将最小需要的容量作为新的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //判断新的容量是否超过ArrayList规定的最大容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //1.如果最小需要的容量超过了最大容量,则新容量大小为Integer.MAX_VALUE
            //2.如果最小需要的容量没有超过最大容量,则新容量大小为MAX_ARRAY_SIZE
            newCapacity = hugeCapacity(minCapacity);
        // 调用Arrays.copyOf方法构建一个新的包含原有元素的大小为新容量大小的Object数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    /**
     * 判断最小需要的容量和ArrayList最大容量的大小
     */
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
  • 🎐 新增元素
   /**
    * 新增元素,返回true
    */
    public boolean add(E e) {
        //调用扩容方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将该元素放到下标为数组元素个数+1的位置
        elementData[size++] = e;
        return true;
    }
    
    /**
     * 向数组指定位置添加元素时,检查指定位置的合法性
     * 若指定位置的大小超过ArrayList中包含元素个数或小于0则抛出索引越界异常
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    /**
     * 向ArrayList指定位置
     *
     * @param index 指定存入ArrayList的位置
     * @param element 新增的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //检查指定位置的合法性
        rangeCheckForAdd(index);
        //调用扩容方法,此时最小需要的容量为元素个数+1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //调用System.arraycopy进行数组的拷贝
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将该元素存入数组的指定位置
        elementData[index] = element;
        //ArrayList元素的个数加1
        size++;
    }
    
    /**
     * 将集合实例中元素全部新增至ArrayList
     *
     * @param c collection 集合实例对象
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        //通过Collection接口的toArray方法得到集合实例的Object数组
        Object[] a = c.toArray();
        //获取集合中元素的个数
        int numNew = a.length;
        //调用扩容方法,此时最小需要的容量 = ArrayList已有的元素个数 + 新增元素的个数
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //通过arraycopy的方法将集合实例中的元素拷贝至ArrayList中
        System.arraycopy(a, 0, elementData, size, numNew);
        //此时ArrayList中元素的个数 = 原有个数 + 集合中元素个数
        size += numNew;
        return numNew != 0;
    }

    /**
     * 将集合实例中元素全部新增至ArrayList的指定位置
     * @param index 新增开始的位置
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        //检查指定位置是否合理
        rangeCheckForAdd(index);
        //通过Collection接口的toArray方法得到集合实例的Object数组
        Object[] a = c.toArray();
        //获取集合中元素的个数
        int numNew = a.length;
        //调用扩容方法,此时最小需要的容量 = ArrayList已有的元素个数 + 新增元素的个数
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //复制的元素个数 = 原有的元素个数 - 指定位置
        //[1,2,3,4,5,6,0,0,0,0] addAll(1,[7,8])
        //[1,0,0,2,3,4,5,6,0,0] 即数组元素从index位置开始整体向后移动numNew位
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //将集合数组的元素拷贝至ArrayList数组
        //[1,7,8,2,3,4,5,6,0,0]
        System.arraycopy(a, 0, elementData, index, numNew);
        //ArrayList的元素个数 = 原有个数 + 集合中元素个数
        size += numNew;
        return numNew != 0;
    }
  • 🎐 Arrays.copyOf 和 System.arraycopy

    在ArrayList源代码的核心方法中多次使用了Arrays.copyof和System.arraycopy,这两个方法有什么联系和区别?

/**
     * Copies the specified array, truncating or padding with nulls (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain <tt>null</tt>.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     * The resulting array is of the class <tt>newType</tt>.
     *
     * @param <U> the class of the objects in the original array
     * @param <T> the class of the objects in the returned array
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @param newType the class of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
     * @throws NullPointerException if <tt>original</tt> is null
     * @throws ArrayStoreException if an element copied from
     *     <tt>original</tt> is not of a runtime type that can be stored in
     *     an array of class <tt>newType</tt>
     * @since 1.6
     */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        //新建一个数组对象
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //调用System.arraycopy将目标数组的元素拷贝到新建的数组中去
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        //返回新的数组对象
        return copy;
    }

  通过分析Arrays.copyof的源码可知,Arrays.copyof内部也是通过System.arraycopy实现数组的拷贝。

    /**
     * 
     * @param src 源数组
     * @param srcPos 源数组的起始位置
     * @param dest 目标数组
     * @param destPos 目标数组的起始位置
     * @param length 拷贝的长度
     */
    public static native void arraycopy(Object src, int  srcPos, Object dest, int destPos, int length);

  通过分析两者的源代码可得出:

  1. Arrays.copyof 是自己在内部新建一个数组,将源数组的内容拷贝到新建的数组中去,并返回新的数组对象;

  2. System.arraycopy需要源数组和目标数组,将源数组的内容拷贝到自定义的数组中;

🎈 总结

  1. ArrayList实现了List接口,为有序集合,允许存储重复的元素和null;
  2. ArrayList内部维护一个动态数组,支持快速随机访问;
  3. ArrayList每次调用add方法新增元素时,都会调用扩容方法判断是否需要扩容,如果需要则扩容为原来容量的1.5倍;
  4. 因为ArrayList的扩容机制需要耗时,因此在存储大量元素时,最好指定最小需要的容量;
  5. ArrayList不是线程安全的,当多个线程对其进行结构上的操作时,程序会抛出ConcurrentModificationException异常,原因在于fast-fail机制,即迭代器在遍历元素时,会使用一个modCount变量,每当迭代器使用hasNext或next前,都会检查modCount的值是否等于期望的值。

 

posted @ 2019-07-09 18:06  学而时习之,不亦说乎?  阅读(577)  评论(0编辑  收藏  举报