美文网首页
Java基础--ArrayList

Java基础--ArrayList

作者: wallany | 来源:发表于2019-05-08 23:44 被阅读0次

    ArrayList简介

    ArrayList实现了List接口,继承了AbstractList,底层是数组实现的。它是非线程安全的,一般多用于单线程环境下(与Vector最大的区别就是,Vector是线程安全的,所以ArrayList 性能相对Vector 会好些),它实现了Serializable接口,因此它支持序列化,能够通过序列化传输(实际上java类库中的大部分类都是实现了这个接口的),实现了RandomAccess接口,支持快速随机访问(只是个标注接口,并没有实际的方法),这里主要表现为可以通过下标直接访问(底层是数组实现的,所以直接用数组下标来索引),实现了Cloneable接口,能被克隆。

    ArrayList的主要成员变量

     private static final int DEFAULT_CAPACITY = 10;//默认初始容量;
     private static final Object[] EMPTY_ELEMENTDATA = {};//定义一个空的数组实例以供其他需要用到空数组的地方调用
     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//定义一个空数组,跟前面的区别就是这个空数组是用来判断ArrayList第一添加数据的时候要扩容多少。默认的构造器情况下返回这个空数组 
     transient Object[] elementData;//数据存的地方它的容量就是这个数组的长度,同时只要是使用默认构造器(DEFAULTCAPACITY_EMPTY_ELEMENTDATA )第一次添加数据的时候容量扩容为DEFAULT_CAPACITY = 10 
     private int size;//当前数组的长度
    

    ArrayList初始化

    ArrayList提供了三种方式

     public ArrayList();//
     public ArrayList(int initialCapacity);//
     public ArrayList(Collection<? extends E> c);//
    

    下面将逐一分析以上三种初始化方式
    首先分析无参构造方法的源码:

       /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    按注释描述无参构造方法将创建一个初始容量为10的空数组,但是从代码可以看出此时只是将elementData指向一个空数组而已。
    再看看带初始容量参数的构造方法:

        /**
         * 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) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    

    从源码可以看出:程序首先会对传进来的参数initialCapacit进行判断,如果初始容量小于0则抛出异常,如果初始容量等于0则将**elementData **指向一个空数组,如果初始容量大于0则创建一个大小为初始容量的数组。
    最后分析带参数的构造方法:

       /**
         * 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)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    这个构造方法是直接将一个collection作为参数,不难看出程序直接将collection转化为数组赋值给elementData,如果elementData的size为0,则将elementData初始化为空数组,size为elementData的长度。这里size是ArrayList的一个int型私有变量,用于记录该list集合中当前元素的数量,注意不是容量。

    ArrayList的Add方法

    前面分析ArrayList的构造函数时,有一个小小的疑问,注释说创建的是一个默认大小为10的空数组,但是代码实现里面看到的只是将elementData赋值为一个空数组。其实这些都是在add方法里面实现的,下面我们分析add方法到底是如何实现的。

        /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
       private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    

    从源码里面可以看出,当调用add方法时首先会调用ensureCapacityInternal方法,从方法名可以看出,该方法是用于确保容量的。通过分析ensureCapacityInternal可看到如下逻辑:

         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    

    通过这一步来判断,当前elementData是否为空数组(即初始化容量为0或者调用了无参构造函数后的结果),如果是,则使用
    Math.max(DEFAULT_CAPACITY, minCapacity)进行选择一个较大的,其中,DEFAULT_CAPACITY是ArrayList定义的静态常量10:可以看出,这里如果minCapacity小于10的话(如果elementData为空的话,size+1即minCapacity一般为1),返回的是10,所以如果没有指定大小的话,默认是初始化一个容量为10的数组。然后在调用ensureExplicitCapacity方法:

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    在这个方法里进行判断,新增元素后的大小minCapacity是否超过当前集合的容量elementData.length,如果超过,则调用grow方法进行扩容。我们进入该方法进行查看:

      /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            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);
        }
    

    在这里可以很清楚的看到扩容容量的计算:int newCapacity = oldCapacity + (oldCapacity >> 1),其中oldCapacity是原来的容量大小,oldCapacity >> 1 为位运算的右移操作,右移一位相当于除以2,所以这句代码就等于int newCapacity = oldCapacity + oldCapacity / 2;即容量扩大为原来的1.5倍,获取newCapacity后再对newCapacity的大小进行判断,如果仍然小于minCapacity,则直接让newCapacity 等于minCapacity,而不再计算1.5倍的扩容。然后还要再进行一步判断,即判断当前新容量是否超过最大的容量 if (newCapacity - MAX_ARRAY_SIZE > 0),如果超过,则调用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;
        }
    

    如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值。否则返回MAX_ARRAY_SIZE。然后回到grow方法,调用Arrays.copyof方法,即复制原数组内容到一个新容量的大数组里。这里Arrays.copyof方法实际是调用System.arraycopy方法。到这里,应该可以很清楚的知道ArrayList底层扩容的原理了。与Vector不同的是,Vector每次扩容容量是翻倍,即为原来的2倍,而ArrayList是1.5倍。

    ArrayLIst的get方法

    /**
         * Returns the element at the specified position in this list.
         *
         * @param  index index of the element to return
         * @return the element at the specified position in this list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
    

    上面是对代码的分析得出的结论,下面将验证分析的结论是否正确

    public class testArrayList {
        public static void main(String[] args){
            ArrayList list = new ArrayList<>();
            System.out.print("size = "+list.size());
            System.out.println("  Capacity = "+getCapacity(list));
            for (int i = 0; i < 20; i++) {
                list.add(i);
                System.out.print("size = "+list.size());
                System.out.println("  Capacity = "+getCapacity(list));
            }
        }
    
        public static int getCapacity(ArrayList list){
            int length = 0;
            Class clazz = list.getClass();
            Field field;
            try{
              field = clazz.getDeclaredField("elementData");
              field.setAccessible(true);
              Object[] objects = (Object[])field.get(list);
              length = objects.length;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                return length;
            }
        }
    }
    

    测试代码中先创建一个ArrayList,然后输出刚创建时list的sizecapacity,然后向list中添加元素,同时打印出list的sizecapacity,结果如下:

    size = 0  Capacity = 0
    size = 1  Capacity = 10
    size = 2  Capacity = 10
    size = 3  Capacity = 10
    size = 4  Capacity = 10
    size = 5  Capacity = 10
    size = 6  Capacity = 10
    size = 7  Capacity = 10
    size = 8  Capacity = 10
    size = 9  Capacity = 10
    size = 10  Capacity = 10
    size = 11  Capacity = 15
    size = 12  Capacity = 15
    size = 13  Capacity = 15
    size = 14  Capacity = 15
    size = 15  Capacity = 15
    size = 16  Capacity = 22
    size = 17  Capacity = 22
    size = 18  Capacity = 22
    size = 19  Capacity = 22
    size = 20  Capacity = 22
    

    可以看出刚创建的时候list的capacitysize的大小都是为0,而添加第一个元素时,size = 0 capacity = 10,不难看出list是在调用add方法时才capacity 才为10;当添加到第11个元素时,因为size>capacity ,所以此时list会扩容,扩容后大小为15,即capacity的1.5倍。

    总结

    ArrayList默认容量是10,如果初始化时一开始指定了容量,或者通过集合作为元素,则容量为指定的大小或参数集合的大小。每次扩容为原来的1.5倍,如果新增后超过这个容量,则容量为新增后所需的最小容量。如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量与限制的容量进行判断,超过则指定为Integer的最大值,否则指定为限制容量大小。然后通过数组的复制将原数据复制到一个更大(新的容量大小)的数组。

    相关文章

      网友评论

          本文标题:Java基础--ArrayList

          本文链接:https://www.haomeiwen.com/subject/qjlnoqtx.html