zoukankan      html  css  js  c++  java
  • ArrayList源码分析

    顶部注释告诉我们的信息

    • ArrayList是实现了List接口的可变长数组(ArrayList的底层是基于数组实现的)

    • ArrayList允许内部存在null元素

    • ArrayList除了不是线程安全的之外,和Vector基本差不多。(如果想使用线程安全的ArrayList:List list = Collections.synchronizedList(new ArrayList(...));或者使用CopyOnWriteArrayList

    • size、isEmpty、get、set、iterator和listIterator方法都以固定时间运行,时间复杂度为O(1)。add和remove方法需要O(n)时间。与用于LinkedList实现的常数因子相比,此实现的常数因子较低。

    • ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。

    • 每个ArrayList实例都有一个capacity属性,随着元素不断add到ArrayList,capacity会自动增加

    • 在添加大量元素之前,可以使用ensureCapacity操作来增加ArrayList的容量,这可以减少递增的重新分配次数(因为grow的原理是使用Arrays.copyof(),一直grow的话肯定效率不高)

    • ArrayList的iterator和listIterator方法返回的迭代器是fail-fast的。

    • fail-fast不能保证一定会被执行的,所以依赖于fail-fast抛出的异常的程序都是错误的写法。fail-fast抛出的异常只能用来帮你detect bugs


    源码部分

    一、定义

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
    • 继承于AbstractList类,AbstractList提供了List接口的骨干实现,以最大程度减少ArrayList实现随机访问所需的工作

    • 实现了RandomAccess接口,这是个Marker Interface,用来表示List支持常数时间内的快速随机访问,用index去遍历实现了RandomAccess接口的类是要比用iterator遍历快的

    • 实现了Cloneable接口,表明其可以调用clone()方法来返回实例的field-for-field拷贝

    • 实现了java.io.Serializable接口,表明该类具有序列化功能(transient关键字表示:这个实例序列化时不要序列化transient修饰的属性)


    二、属性

    //1.序列化id
    private static final long serialVersionUID = 8683452581122892189L;
    //2.默认的初始容量
    private static final int DEFAULT_CAPACITY = 10;
    //3.存放空实例的空数组(当指定该ArrayList容量为0的时候,返回该空数组)
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //4.存放capacity为默认大小的空实例的数组,比较4和3来确定:当给5中加入第一个元素的时候,5应该有多大
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //5.当前数据存放的位置,这个数组是不参与序列化的,ArrayList有自己专门的序列化方法
    transient Object[] elementData;
    //6.数组的长度,等于elementData.length
    private int size;
    //7.数组最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    

    关于属性要注意的几点:

    • 除了上面的几个属性之外,还有一个从AbstractList继承过来的modCount属性,代表ArrayList修改的次数

    • 指定ArrayList容量为0的时候,返回EMPTY_ELEMENTDATA数组

    • DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组是ArrayList的无参构造方法返回的数组。

    • DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别是:第一个是无参构造方法返回的(即默认返回的),第二个是当用户指定容量为0的时候返回的

    • 当elementData的值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,当第一次添加元素进入ArrayList中时,数组将扩容值DEFAULT_CAPACITY

    • elementData被标记为transient,ArrayList的序列化和反序列化不依赖于这个数组,详情请查看writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectOutputStream s)方法。


    三、构造方法

    无参构造方法:构造一个初始容量为10的空数组

    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    有参构造方法,参数为容量:构造一个容量为initialCapacity的空ArrayList

    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);
            }
        }
    

    有参构造方法,参数为Collection:构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

    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;
            }
        }
    

    步骤:

    (1)将collection对象转换成数组,然后将数组的地址赋值给elementData

    (2)更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData

    (3)如果size的值大于0,判断toArray方法是否成功了。如果没成功则执行Arrays.copyof方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。

    注意:this.elementData = arg0.toArray(); 这里执行的简单赋值时浅拷贝,所以要执行Arrays,copyof做深拷贝


    四、其他核心方法

    get方法

    ArrayList底层为数组,get方法很简单:先判断一下是否越界,之后直接通过数组下标来获取元素,时间复杂度为O(1),代码如下:

    public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
    }
    
    E elementData(int index) {
            return (E) elementData[index];
    }
    
    private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    

    set方法:和get类似,时间复杂度为O(1),代码如下:

    public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
    

    contains方法

    注意:这里的indexof方法是用数组索引做的,并不是像AbstractList中的indexof的实现方法(iterator遍历实现的)

    public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
    
    public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    public int lastIndexOf(Object o) {
            if (o == null) {
                for (int i = size-1; i >= 0; i--)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = size-1; i >= 0; i--)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    

    在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。

    add(E e)方法:在ArrayList的末尾加入元素e,时间复杂度O(1)

    注意:只加1,保证资源不被浪费

    public boolean add(E e) {
    
            //确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

    add(int index, E element):在指定index下插入元素,时间复杂度O(n)

    public void add(int index, E element) {
            rangeCheckForAdd(index);
    
            //确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    

    remove:时间复杂度O(n)

    public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(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
    
            return oldValue;
        }
    
    public boolean remove(Object o) {
            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++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
    private void fastRemove(int index) {
            modCount++;
            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
        }
    

    注意:为了让GC起作用,必须显式的为最后一个位置赋null值。上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。

    clear

    public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    

    sort方法

    public void sort(Comparator<? super E> c) {
            final int expectedModCount = modCount;
            Arrays.sort((E[]) elementData, 0, size, c);
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }
    

    五、扩容(很重要,核心部分)

    ensureCapacity方法

    这个方法是在外部使用的ensure容量方法(public)

    情况1:在刚刚执行完ArrayList无参构造方法时,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA。如果还没对这个list增加元素之前执行了ensureCapacity方法,将minExpand赋值为10,比较传进来的参数和10的大小,如果传进来的参数大于10,执行ensureExplicitCapacity,如果传进来的参数小于10,就啥也不做了

    情况2:elementData不为空时(已经执行过add方法),minExpand=0,只要传进来的参数大于0,就一定会执行ensureExplicitCapacity

         /**
         * @param   minCapacity   the desired minimum capacity
         */
        public void ensureCapacity(int minCapacity) {
            int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                ? 0
                : DEFAULT_CAPACITY;
    
            if (minCapacity > minExpand) {
                ensureExplicitCapacity(minCapacity);
            }
        }
    

    ensureExplicitCapacity方法(private的)

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

    grow方法

    private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
    
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
    • 将容量变成原来的1.5倍,如果变成1.5还比想要的小,就将容量变成想要的;如果变成1.5倍后比MAX_ARRAY_SIZE大(这个是前面提到的ArrayList的属性),执行hugeCapacity方法,返回newCapacity

    • 在确定好newCapacity之后,执行Arrays.copyof方法,将返回的数组赋值给elementData

    • hugeCapacity方法如下:

    private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
    • 为什么MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;呢?因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。

    ensureCapacityInternal方法(顾名思义在内部使用的,add使用的就是这个ensure方法,private)

    和ensureCapacity方法类似,也是分两种情况,如果elementData内已经包含元素了,就用minCapacity去ensureExplicitCapacity

    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));
        }
    

    扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。


    六、序列化与反序列化

    • ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。

    • 每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小

    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioural compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            elementData = EMPTY_ELEMENTDATA;
    
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in capacity
            s.readInt(); // ignored
    
            if (size > 0) {
                // be like clone(), allocate array based upon size not capacity
                int capacity = calculateCapacity(elementData, size);
                SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
                ensureCapacityInternal(size);
    
                Object[] a = elementData;
                // Read in all elements in the proper order.
                for (int i=0; i<size; i++) {
                    a[i] = s.readObject();
                }
            }
        }
    
    

    七、迭代器

    • iterator()方法返回的是内部类Itr的实例,Itr是An optimized version of AbstractList.Itr

    • listIterator()方法返回的是内部类ListItr的实例,An optimized version of AbstractList.ListItr

    主要讲一下fail-fast:

    modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

    在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。


    八、System.arraycopy 和 Arrays.copyOf

    • System.arraycopy底层是c实现的

    • Arrays.copyOf底层也是调用System.arraycopy实现的


    九、总结

    • ArrayList基于数组方式实现,无容量的限制(会扩容)

    • 添加元素时可能要扩容(所以最好预判一下)

    • 删除元素时不会减少容量(若希望减少容量,trimToSize())

    • 删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间

    • remove(Object o)需要遍历数组

    • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高

    • 线程不安全,使用iterator遍历可能会引发多线程异常

  • 相关阅读:
    C# Graphics类详解
    c#画直线
    c#画直线
    基础练习 数列特征
    基础练习 数列特征
    基础练习 字母图形
    基础练习 字母图形
    基础练习 字母图形
    基础练习 01字串
    基础练习 01字串
  • 原文地址:https://www.cnblogs.com/swifthao/p/12925739.html
Copyright © 2011-2022 走看看