zoukankan      html  css  js  c++  java
  • ArrayList源码解析--值得深读

    ArrayList源码解析

    基于jdk1.8

    ArrayList的定义

    类注释

    • 允许put null值,会自动扩容;
    • size isEmpty、get、set、add等方法时间复杂度是O(1);
    • 是非线程安全的,多线程情况下推荐使用CopyOnWriteArrayList或者Vector。
    • 增强for循环或使用迭代器过程中,如果数组大小被改变会抛出异常。
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    从源码中我们可以看出,ArrayList继承了AbstractList,实现了List接口,RandomAccess,Cloneable,Serializable接口。

    在这里插入图片描述

    实现List接口提供了元素的添加、删除。修改。遍历等功能。
    实现RandomAccess接口,他是一个标志性接口,表明实现这个接口后,List集合支持随机访问。
    实现Cloneable接口表明ArrayList可以被克隆
    实现Serializable接口表示ArrayList支持序列化,能被传输。

    ArrayList是一个动态数组,与普通数组相比他的容量可以动态的扩容。

    ArrayList的有关属性

     private static final long serialVersionUID = 8683452581122892189L;
    
        /*默认容量大小是10*/
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * 空数组
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        /**
         * 用来共享空数组实例
         * 我们把它和EMPTY_ELEMENTDATA数组区分出来,当添加第一个元素的时候知道需要扩增多少容量
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        /**
         * 保存ArrayList数据的数组
         */
        transient Object[] elementData; // non-private to simplify nested class access
    
        /**
         * 集合包含元素的个数
         */
        private int size;
    
    

    ArrayList的构造函数

    public ArrayList(int initialCapacity) {
            // 如果创建的时候传入的有值,并且大于0,就直接创建大小是传入的值的数组
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
            //如果创建的时候没有指定大小,就是一个空数组
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
            //传入负数会抛出非法参数异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
        /**
         * 构造一个空数组,当增加第一个元素的时候才确定数组容量是10
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         
         *构造函数的参数是一个集合,如果集合为null,会抛空指针异常
         *
         */
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            //如果集合的大小不为0 就通过Arrays.copy方法,把Object数组的元素一一复制到elementData数组中
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                // 注意这是java的一个bug,c.toArray()方法返回值可能不是Object数组,这里转成Object类型
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
            //如果集合里没有元素就是一个空数组
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    上面的那个// c.toArray might (incorrectly) not return Object[] (see 6260652)一般情况下都不会触发这个bug ,这里演示一下是怎么出现的。

    public void testConstructor() {
            List<String> arrayList = Arrays.asList("Hello");
            //toArray方法返回Object数组类型
            Object[] objects = arrayList.toArray();
            log.info(objects.getClass().getSimpleName());
            // 打印出来是 String[]
    
            //这样写是对的
            //objects[0]="Java";
            
            //这样就会报错因为数组元素的类型是String
            objects[0] = new Object();
        }
    

    不过这个bug已经在Java9解决。

    新增和扩容的实现

    新增元素

    
    /**
         * Appends the specified element to the end of this list.
         *增加元素到集合的末尾
         * 
         */
        public boolean add(E e) {
               //先扩容,使容量+1
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
        /**
         * 在指定位置增加元素
         *
         * @param index index at which the specified element is to be inserted
         * @param element element to be inserted
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public void add(int index, E element) {
           //检查增加的位置是否越界
           rangeCheckForAdd(index);
            //扩容,size+1
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //元素的复制,将 index之后的元素往后 移动一位,size++;
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
                             //最后在index位置上赋值
            elementData[index] = element;
            size++;
        }
    
    

    扩容

    
     private void ensureCapacityInternal(int minCapacity) {
            //确保容积足够
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
    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) {
           //记录数组被修改的次数加1 
           modCount++;
    
            // 如果我们期望的数组大小大于目前的数组长度,那么就扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        //扩容方法
         private void grow(int minCapacity) {
            // 先记录旧的数组大小
            int oldCapacity = elementData.length;
            //扩容后的容量=扩容前的+扩容前的大小/2,也就是说扩容后的值是原来的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果扩容后的值,小于期望的大小,那扩容后的值就改为期望值
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            
            //如果扩容后的值大于jvm所能分配的最大值,那么就用Integer.MAX_VALUE.,否则等于MAX_ARRAY_SIZE
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 最后数组复制,底层是System.arraycopy()方法
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
    
    • 扩容后数组的容量是原先数组容量的1.5倍。
    • ArrayList中的数组长度最大是Integer.MAX_VALUE,超过这个值,JVM就不会给数组分配内存空间了。
    • 新增时,并没有对新增元素进行严格校验,所以可以新增null。

    其实我们可以看出,大多数方法中都用到了Arrays.copyOf方法

    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(
            original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }
    

    再看System.arraycopy

    //src 是原数组,srcPos是原数组的位置,dest是目标数组,destPOS是目标数组的位置,length是拷贝的长度,这个方法是native方法,所以底层可能是C/C++编写的
    public static native void arraycopy(Object src,  int  srcPos,
                                            Object dest, int destPos,
                                            int length);
    

    删除和迭代

    ArrayList支持两种删除元素的方式

    1. remove(int index) 按照下标删除
    2. remove(Object o) 按照元素删除,删除第一个匹配到的元素。
    public E remove(int index) {
            //数组下标越界检查
            rangeCheck(index);
            //修改次数自增1
            modCount++;
            //记录被删除的值
            E oldValue = elementData(index);
           // 记录将要从index后移动的多少个位数到前面,因为size是从0开始,index是从1开始所以需要减一
            int numMoved = size - index - 1;
            if (numMoved > 0)
                //从elementData的index+1处拷贝,拷贝到elementData数组中,从index处开始放置拷过来的元素,拷贝的长度是 numMoved
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // 最后一个元素赋值为null,帮助GC
    
            return oldValue;
        }
    
        /**
         根据值删除元素
         */
        public boolean remove(Object o) {
        //y因为ArrayList允许元素值为null,所以删除的时候遍历一次找到第一个是null的值,然后移除。
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        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 remove method that skips bounds checking and does not
         * return the value removed.
         */
        private void fastRemove(int index) {
        //记录修改数加1
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; //最后元素赋值成null,有助于GC
        }
    
        /**
            移除集合中所有元素
         */
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                //将集合中的每一个元素赋值为null
                elementData[i] = null;
            //集合元素的个数设置成0
            size = 0;
        }
    
    
    • 新增时因为没有对null做校验所以可以新增null,删除的时候就需要对null值做判断了,按照值删除元素实际上还是根据值找到这个元素的索引,进行删除。比较的方法是通过equals方法。
    • 某一个元素被删除后,ArrayList采用的是直接将这个元素后面的所有元素复制到原数组中,最后的一个元素的值设置成null,让JVM进行GC操作。

    接下来看迭代

    
    public ListIterator<E> listIterator(int index) {
        //如果索引越界直接抛异常IndexOutOfBoundsException,否则返回从指定下标开始的所有元素列表(按熟顺序),ListIterator接口继承了Iterator接口
            if (index < 0 || index > size)
                throw new IndexOutOfBoundsException("Index: "+index);
            return new ListItr(index);
        }
    
        /**
         * 返回列表中的列表迭代器按顺序返回,迭代器是fail-fast
         */
        public ListIterator<E> listIterator() {
            return new ListItr(0);
        }
    
        /**
         * 以正确顺序返回元素列表的迭代器
         */
        public Iterator<E> iterator() {
            return new Itr();
        }
    
    //ltr是ArrayList的内部类,实现了 Iterator接口,这里只是Itr的部分代码
     private class Itr implements Iterator<E> {
            int cursor;       // i迭代过程中,下一个元素的位置,默认从0开始
            int lastRet = -1; // 新增时表示上次迭代过程中,索引的位置;删除场景下是-1
            int expectedModCount = modCount;
            //期望修改的版本号=实际的修改版本号
            Itr() {}
    
            public boolean hasNext() {
            //判断下一个元素存不存在,只需知道下一个索引的下标是不是数组大小,如果不是返回true,如果是返回false
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
            //检查版本号是不是已经被修改
                checkForComodification();
                int i = cursor; //下一个元素的下标
                if (i >= size) 
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
               //下一次迭代时元素的位置
               cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
    
    
            public void remove() {
            //当lastRet小于0,也就是说在多个线程操作时,直接将迭代器中的列表删完了,里面没有元素了,就会抛出IllegalStateException
                if (lastRet < 0)
                    throw new IllegalStateException();
               //迭代过程中判断版本号有没有被修改,如果被修改抛出ConcurrentModificationException
               checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    //lastRet=-1防止重复删除
                    lastRet = -1;
                    //下次迭代时期望的版本号和实际的版本号一致
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    //版本号比较
    final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    
    

    小结

    • ArrayList底层是使用类似于动态数组实现的,默认构造方法初始化的容量是10,但是在没有指定容量大小时区add第一个元素时才会确定出数组的容量。
    • 扩容时,扩容后的长度是扩容前的1.5倍
    • 实现了RandomAccess接口,支持随机读写,get读取元素的性能较好
    • 线程时不安全的,是因为自身的elementData、size、modCount在进行各种操作时,都没有加锁
    • 新增(这里的新增默认是增加到尾部)和删除方法 的时间复杂度是O(1)

    以上理解有误的地方欢迎指出,共同进步!

  • 相关阅读:
    Microsoft.NET User Group
    白话MVP 和 MVVM 【转】
    高效的二分法TOP MAX/TOP MIN分页存贮过程
    策略模式5
    说说我们项目组的例行会议
    合格的项目经理
    说说我们的招聘和面试
    web安全问题汇总
    ASP.NET中常用的优化性能方法
    说说我们安排的培训
  • 原文地址:https://www.cnblogs.com/dataoblogs/p/14121934.html
Copyright © 2011-2022 走看看