zoukankan      html  css  js  c++  java
  • ArrayList的实现原理

    1.概述

    ArrayList其实可以理解为一个动态数组,是一个复杂的Array。与普通的数组相比,它仅能存储对象(普通的数组可以存储对象和基本类型的元素),而且它是动态的,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容。每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝。因此,如果可预知数据量的多少,可在构造 ArrayList 时指定其容量。在添加大量元素前,应用程序也可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量,从而提高性能。

    应该注意的是,ArrayList的实现不是同步的。在多线程的环境下,如果有多个线程同时访问一个ArrayList,而其中至少有一个线程对该ArrayList的结构进行了修改,那么它必须保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)因此,ArrayList建议只在线程安全的环境下使用。在多线程的环境下,建议使用concurrent包下的CopyOnWriteArrayList,或者使用Collections包下的synchronizedList来把ArrayList包装起来。例如:

    List<String> list = Collections.synchronizedList(new ArrayList<>());

    2.实现原理

    2.1 类的定义以及一些属性:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
        {
        
            private static final int DEFAULT_CAPACITY = 10;
            private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
            transient Object[] elementData; // non-private to simplify nested class access
            private int size;

    在ArrayList这个类的定义中,我们可以看到,它是继承于AbstractList这个类的,

    ArrayList实现了List接口,而List是一个数组队列,提供了相关的增、删、改、查等功能;

    ArrayList实现了RandomAccess接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在 ArrayList 中,我们即可以通过元素的序号(相当于数组的下标)快速获取元素对象;这就是快速随机访问;

    ArrayList实现了Cloneable接口,意味着它实现了clone()方法,即ArrayList能够被克隆;

    ArrayList实现了 java.io.Serializable 接口,这意味着 ArrayList 支持序列化,能通过序列化去传输。

    ArrayList的实现关系:

    再来看一下ArrayList的成员变量:

    elementData:用来存储ArrayList里面的元素;

    size:用来表示ArrayList的大小,即ArrayList里面存储了多少对象;

    DEFAULT_CAPACITY:这个貌似是JDK1.8中新增的,表示ArrayList的默认大小。

     2.2 ArrayList的构造函数:

        /**
         * 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);
            }
        }
        
         /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         * 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;
            }
        }

    根据每个构造函数的注释即可看到:

    public ArrayList():构造一个初始容量为10的空列表;

    public ArrayList(int initialCapacity):构造一个指定初始容量的空列表;

    public ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

    2.3 ArrayList的增删改查

    1)ArrayList的增

    ①:

        /**
         * 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(int minCapacity)方法来判断当前容量是否足够大
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;      
            return true;
        }
        
        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++;
    
            // overflow-conscious code
            //如果当前容量已经大于数组长度,则调用grow(int minCapacity)方法进行扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        
        /**
         * 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);
        }
        

    add(E e):这个方法是将元素加入到ArrayList的尾部,在添加元素之前,会检查容器的容量,当容量不足时,会调用grow(int minCapacity)方法进行扩容。

    ②:

        /**
         * Inserts the specified element at the specified position in this
         * list. Shifts the element currently at that position (if any) and
         * any subsequent elements to the right (adds one to their indices).
         *
         * @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);
            //检查容量大小
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //index后面的元素后移一位
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    
       private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    add(int index, E element):在指定下标插入元素。可以看到,在插入元素之前,先会检查传入的下标是否满足要求,然后再检查容量是否足够。当以上这两个条件都已经满足时,会调用System.arraycopy()方法,将index后面的元素都后移一位,然后再在index插入新的元素。

    ③:

        /**
         * Appends all of the elements in the specified collection to the end of
         * this list, in the order that they are returned by the
         * specified collection's Iterator.  The behavior of this operation is
         * undefined if the specified collection is modified while the operation
         * is in progress.  (This implies that the behavior of this call is
         * undefined if the specified collection is this list, and this
         * list is nonempty.)
         *
         * @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 NullPointerException if the specified collection is null
         */
        public boolean addAll(Collection<? extends E> c) {
            Object[] a = c.toArray();
            int numNew = a.length;
            ensureCapacityInternal(size + numNew);  // Increments modCount
            System.arraycopy(a, 0, elementData, size, numNew);
            size += numNew;
            return numNew != 0;
        }
    
        /**
         * Inserts all of the elements in the specified collection into this
         * list, starting at the specified position.  Shifts the element
         * currently at that position (if any) and any subsequent elements to
         * the right (increases their indices).  The new elements will appear
         * in the list in the order that they are returned by the
         * specified collection's iterator.
         *
         * @param index index at which to insert the first element from the
         *              specified collection
         * @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);
    
            Object[] a = c.toArray();
            int numNew = a.length;
            ensureCapacityInternal(size + numNew);  // Increments modCount
    
            int numMoved = size - index;
            if (numMoved > 0)
                System.arraycopy(elementData, index, elementData, index + numNew,
                                 numMoved);
    
            System.arraycopy(a, 0, elementData, index, numNew);
            size += numNew;
            return numNew != 0;
        }

    addAll(Collection<? extends E> c) 和 addAll(int index, Collection<? extends E> c):将特定 Collection 中的元素添加到 Arraylist 中,和前面的两种插入方式大同小异。

    在ArrayList中,add()方法的本质是在特定的位置插入新的元素,但其中也会涉及到数组容量不够而动态增长的问题。

    2)ArrayList的删

        /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).
         *
         * @param index the index of the element to be removed
         * @return the element that was removed from the list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E remove(int index) {
            //检查传入的下标是否大于数组的大小
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            //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 remove method that skips bounds checking and does not
         * return the value removed.
         */
        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
        }
        
        /**
         * Checks if the given index is in range.  If not, throws an appropriate
         * runtime exception.  This method does *not* check if the index is
         * negative: It is always used immediately prior to an array access,
         * which throws an ArrayIndexOutOfBoundsException if index is negative.
         */
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    remove(int index):删除特定位置上的元素,在传入index时,会检查是否越界,如果在正常范围内,则把index后面的元素往前移一位

    remove(Object o):删除指定的元素:如果要删除的对象为null,则遍历ArrayList,找到第一个为null的元素并删除;如果元素不为null,则遍历元素,找到第一个与o相同的元素并删除(ArrayList中可以包含null并且允许有重复的值)

    删除元素的方法也很简单,把指定元素后面的元素分别往前移一位,并且将size-1位置上的元素置为null即可。

    3)ArrayList的改

        /**
         * Replaces the element at the specified position in this list with
         * the specified element.
         *
         * @param index index of the element to replace
         * @param element element to be stored at the specified position
         * @return the element previously at the specified position
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }

    set(int index, E element):该方法首先调用rangeCheck(index);来校验 index 变量是否超出数组范围,超出则抛出异常。而后,取出原 index 位置的值,并且将新的 element 放入 Index 位置,返回 oldValue。

    4)ArrayList的查

        /**
         * Returns the index of the first occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the lowest index <tt>i</tt> such that
         * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
         * or -1 if there is no such index.
         */
        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;
        }
        /**
         * Returns the index of the last occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the highest index <tt>i</tt> such that
         * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
         * or -1 if there is no such index.
         */
        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;
        }
    
        /**
         * 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);
        }

    indexOf(Object o):查找ArrayList中是否包含此元素,如果包含,则返回此元素第一次出现的下标,否则返回-1;

    lastIndexOf(Object o):这个方法与上面的方法不同的是,如果ArrayList包含要查找的元素,返回它最后一次出现的位置,否则返回-1;

    get(int index):这个方法较为简单。首先判断传入的下标是否越界,然后返回数组中下标值为index的元素即可。

    2.4 ArrayList的扩容

        /**
         * Increases the capacity of this <tt>ArrayList</tt> instance, if
         * necessary, to ensure that it can hold at least the number of elements
         * specified by the minimum capacity argument.
         *
         * @param   minCapacity   the desired minimum capacity
         */
        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++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        
        /**
         * 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);
        }
    
        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里面添加元素的时候已经见到过扩容的代码。每一次添加元素,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。而我们有两个方法进行扩容:一种是开发者自己调用ensureCapacity(int minCapacity)这个方法来主动进行扩容;另外一种方法是,当在往ArrayList里面添加元素的过程中,数组容量不足,那么它自身会调用grow(int minCapacity)方法来进行扩容,以满足容量的需求。

    我们在上面的源码中可以看到,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的 1.5 倍(从int newCapacity = oldCapacity + (oldCapacity >> 1);)这行代码得出)。每一次的扩容,都要将数据拷贝到新数组中,这样无疑会大大降低性能。因此,我们在使用ArrayList的时候,应尽量避免对其进行扩容。(如果能够事先估计元素的数量时,最好在初始化的时候指定其容量)

    2.4 Fail-Fast机制

    ArrayList也采用了fail-fast机制。在多线程的环境中,面对并发的修改时,迭代器会很快失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

    fail-fast机制在HashMap的使用及其实现原理有过介绍,这里就不再重复了^_^

  • 相关阅读:
    Node.js:事件循环
    Node.js:回调函数
    Node.js:REPL(交互式解释器)
    Node.js:NPM 使用介绍
    Node.js:创建第一个应用
    Node.js:安装配置
    Node.js:教程
    Node.js:目录
    Node.js:template
    虚拟化之xenserver
  • 原文地址:https://www.cnblogs.com/WakingShaw/p/13712025.html
Copyright © 2011-2022 走看看