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

    ArrayList关键属性分析

    ArrayList采用Object数组来存储数据

        /**

         * The array buffer into which the elements of the ArrayList are stored.

         * The capacity of the ArrayList is the length of this array buffer. 何问起 hovertree.com

         */

        transient Object[] elementData; // non-private to simplify nested class access

        /**

         * The size of the ArrayList (the number of elements it contains).

         * @serial

         */

        private int size;

    Object[] elementData是一个buffer数组,用来存储ArrayList的数据,该数组的大小表示ArrayList的容量,而size属性表示的是ArrayList里边存储元素的个数。

        /**

         * Shared empty array instance used for default sized empty instances. We

         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when

         * first element is added.何问起 hovertree.com

         */

        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    多个ArrayList实例共享的static属性,一个空数组的实例,使用ArrayList的无参构造函数创建ArrayList实例的时候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA给底层数组elementData赋值

        /**

         * Constructs an empty list with an initial capacity of ten.

         */

        public ArrayList() {

            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

        }

    而当创建一个容量为0的ArrayList时,直接将层数组elementData赋值为另外一个static属性

        /**

         * Shared empty array instance used for empty instances.

         */

        private static final Object[] EMPTY_ELEMENTDATA = {};

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

            }

        }

    从ArrayList获取数据get(int index)

    ArrayList可以通过下标对数据进行随机访问,时间复杂度O(1),实现方法为get方法

        public E get(int index) {

            rangeCheck(index);

            return elementData(index);

        }

    get方法很简单,输入参数合法的话直接返回底层数组elementData对应位置的元素即可。 
    而rangeCheck主要是检查下标不能越界访问

        private void rangeCheck(int index) {

            if (index >= size)

                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

    向ArrayList尾部添加一个数据add(E e)

    添加数据有两个方法,调用add(E e) 向ArrayList末尾添加数据和调用add(int index, E element)添加数据到指定位置

        public boolean add(E e) {

            ensureCapacityInternal(size + 1);  // Increments modCount!!

            elementData[size++] = e;

            return true;

    }

    添加数据首先检查是不是需要扩充容量来添加新的数据,调用ensureCapacityInternal(size + 1)确保当前容量足够添加一个新的数据,然后elementData[size++] = e将新数据添加在数组的末尾

        private void ensureCapacityInternal(int minCapacity) {

            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

            }

            ensureExplicitCapacity(minCapacity);

     }

    ensureCapacityInternal确保在ArrayList容量为0的时候添加数据扩容时,至少扩容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小默认为10

        /**

         * Default initial capacity.

         */

        private static final int DEFAULT_CAPACITY = 10;

    然后调用 ensureExplicitCapacity(minCapacity)进行扩容

        private void ensureExplicitCapacity(int minCapacity) {

            modCount++;

            // overflow-conscious code

            if (minCapacity - elementData.length > 0)

                grow(minCapacity);

        }

    modCount++用于记录修改次数,主要用于一个线程使用迭代器迭代数据的时候另一个数据更改ArrayList结构抛出ConcurrentModificationException异常 
    if (minCapacity - elementData.length > 0)用于判断是否真的需要扩容,elementData.length表示的是容器容量,size表示容器存储数据的数量,而minCapacity =sie+1,因此elementData如果有多于一个位置空闲没有存储数据,就不需要扩容 
    否则调用grow(minCapacity)进行真正的扩容

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

        }

    可以看到扩容时直接将容量大小变为之前的1.5倍

    int newCapacity = oldCapacity + (oldCapacity >> 1);

    这里还需要对newCapacity进行调整,如果扩容后的newCapacity还是比minCapacity小 
    满足

    if (newCapacity - minCapacity < 0)

    设置newCapacity为传进来的参数minCapacity

    newCapacity = minCapacity;

    然后判断现在的newCapacity是否比上限MAX_ARRAY_SIZE大

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    如果newCapacity比MAX_ARRAY_SIZE还大,则需要调用hugeCapacity(minCapacity)判断是否是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;

        }

    hugeCapacity(int minCapacity)首先检测是否溢出,没溢出的话就返回Integer.MAX_VALUE 作为最大值(minCapacity > MAX_ARRAY_SIZE条件在调用出就满足)

    经过上述一系列步骤,最终满足各种条件的新容量值minCapacity得到满足

       // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

    这里是直接创建一个容量为newCapacity的新数组,把之前elementData保存的数据复制到新数组。数组的复制是比较耗费性能的,因此应该避免连续扩容,尽量调用有参构造函数ArrayList(int initialCapacity)并设置合理容量大小

    向ArrayList任意位置添加一个数据add(int index, E element)

    调用add(int index, E element)在ArrayList任意位置添加数据

        public void add(int index, E element) {

            rangeCheckForAdd(index);

            ensureCapacityInternal(size + 1);  // Increments modCount!!

            System.arraycopy(elementData, index, elementData, index + 1,

                             size - index);

            elementData[index] = element;

            size++;

        }

    传入参数合法性检查仍然是方法体第一件要做的事情,这里要检测插入数据的位置index是否合法

        private void rangeCheckForAdd(int index) {

            if (index > size || index < 0)

                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

     }

    参数合法性通过后调用ensureCapacityInternal(size + 1)进行扩容,上边已经分析过扩容的固定套路。扩容后就是调用System.arraycopy进行数组的复制,由此可见ArrayList添加数据是比较耗费性能的,事件复杂度是O(n),因为插入数据总是伴随着数组的复制(数组元素的移动)。

    在ArrayList尾部添加一个Collection集合addAll

        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;

        }

    先通过Collection的toArray方法把Collection转化为Object数组,然后通过ensureCapacityInternal(size + numNew)进行扩容,然后调用

    System.arraycopy(a, 0, elementData, size, numNew); 
    public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

    System.arraycopy进行数组的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示从数组a的下标0出开始复制length个元素导数组elementData的下标size处。

    在ArrayList任意位置添加一个集合Collection

     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;

    }

    仍然是先做入口参数的合法性检查

        private void rangeCheckForAdd(int index) {

            if (index > size || index < 0)

                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        }

    然后把集合转化为Object数组

    Object[] a = c.toArray();

    通过ensureCapacityInternal(size + numNew)进行扩容 
    计算原数组需要移动的数据的个数int numMoved = size - index 
    如果有数据需要移动,调用System.arraycopy进行数据整体移动

    System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

    从elementData数组下标为index出复制numMoved个数据到elementData数组的下标为index + numNew处 
    这样elementData数组下标为index到index+numNew-1的位置被空出来,用于放置新的数据 
    然后集合的数据添加到elementData数组留出的位置即可

    System.arraycopy(a, 0, elementData, index, numNew);

    从数组a的下标为0的地方复制numNew个数据到elementData数组的下标为index处

    ArrayList中获取某一数据的下标indexOf

        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;

        }

    获取下标只能通过遍历的方式逐一比较,null数据不能调用方法,所以不能通过equals进行比较,所以分类讨论, 
    如果传入的参数o是是null的话通过elementData[i]==null进行比较,否则通过o.equals(elementData[i])进行比较。 
    如果遍历过程中找到该数据,返回该数据下标,遍历结束没有找到返回-1 
    注意indexOf是找到第一个相等的数据就直接返回下标了,如果想找到该数据在ArrayList中的最后一个位置,使用lastIndexOf即可

    清空ArrayList的全部数据clear()

        public void clear() {

            modCount++;

            // clear to let GC do its work

            for (int i = 0; i < size; i++)

                elementData[i] = null;

            size = 0;

        }

    首先modCount++,这是如果其他线程正在通过迭代器进行数据的访问,检测到modCount发生了变化将会抛出ConcurrentModificationException 
    然后设置数组中的每一个元素为null,方便垃圾回收,最后设置size=0; 
    虽然数据全部置null,size归0,但是elementData的大小没有变化

    ArrayList的克隆clone()

        /**

         * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The

         * elements themselves are not copied.)

         *

         * @return a clone of this <tt>ArrayList</tt> instance

         */

        public Object clone() {

            try {

                ArrayList<?> v = (ArrayList<?>) super.clone();

                v.elementData = Arrays.copyOf(elementData, size);

                v.modCount = 0;

                return v;

            } catch (CloneNotSupportedException e) {

                // this shouldn't happen, since we are Cloneable

                throw new InternalError(e);

            }

        }

    可以看到clone方法只是进行了浅克隆,在某些需要深克隆的场景出,需要自己负责每一个元素的深克隆

    ArrayList删除指定位置的数据

        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;

    }

    代码流程还是比较清晰 
    下标检查,modCount++达到迭代器快速失败,数组元素的移动,返回旧值 
    其中比较重要的一点就是

    elementData[–size] = null; // clear to let GC do its work

    Effective Java提到了这点,自己申请内存自己要记得管理,否则造成内存泄露

    ArrayList删除某一个数据

        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;

        }

    这里同样是根据传入的参数o是否为null进行分类处理,找到元素所在的位置index后直接调用 fastRemove(index)进行数据的删除,fastRemove比remove不需要检查数组下标

    ArrayList的排序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++;

     }

    先保存modCount,如果modCount在排序过程中被改变,抛出ConcurrentModificationException异常 
    排序是直接调用Arrays.sort方法

    ArrayList的缩容trimToSize()

        public void trimToSize() {

            modCount++;

            if (size < elementData.length) {

                elementData = (size == 0)

                  ? EMPTY_ELEMENTDATA

                  : Arrays.copyOf(elementData, size);

            }

       }

    先登记modCount++是迭代器可以快速失败 
    然后通过 Arrays.copyOf进行数组创建和复制,使ArrayList的容量等于保存的数据的数量的大小

    推荐:http://www.cnblogs.com/roucheng/p/javatimu.html

  • 相关阅读:
    sp_addlinkedserver的一些操作
    MVC3.0,路由设置实现伪静态IIS中404错误
    Chrome本地跨域请求设置,实现HTML模板页
    jQuery异步请求(如getJSON)跨域解决方案
    【海淘域名】GoDaddy账户被锁定后的解决方法
    jquery-qrcode客户端二维码生成类库扩展--融入自定义Logo图片
    JavaScript获取URL参数方法总汇
    ASP.NET 内联代码、内联表达式、数据绑定表达式使用方法罗列(形式就是常说的尖括号 百分号 等于号 井号)
    如何获取Repeater行号(索引)、记录总数?
    判断jquery类库是否加载,如未加载则加载。
  • 原文地址:https://www.cnblogs.com/roucheng/p/jdkfenxi.html
Copyright © 2011-2022 走看看