zoukankan      html  css  js  c++  java
  • ArrayList源码详解

    必要的初始知识

    System.arraycopy这个东西最好要知道一下,这是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(负数)
         */
          // 用初始长度去声明一个空的列表(list),initialCapacity固初始化长度,IllegalArgumentException如果给的值不合理(负数),就抛出这个错误.
        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.
         *创建一个列表(list)包含一个已经指定元素的集合,用由那个集合的迭代器按顺序返回
         * @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();//将输入的集合化为数组--Object[]数组.
            //如果输入的集合不为空
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                //对于这段话的解释https://www.cnblogs.com/gilbertbright/p/11714334.html
                if (elementData.getClass() != Object[].class)
                    //如果输入的内容不是object类型,则要按照那个类型去进行赋值.
                    //original - 要复制的数组 
                   //newLength - 要返回的副本的长度 
                   //newType - 要返回的副本的类 
                    //将elementData到Object数组中,即让他们的类型产生变化
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    c.toArray might (incorrectly) not return Object[] (see 6260652)是什么意思

    https://www.cnblogs.com/liqing-weikeyuan/p/7922306.html 这个也是个很好的问题

    本类的成员变量

       private static final long serialVersionUID = 8683452581122892189L;
    
        /**
         * Default initial capacity.初始化长度.  
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * Shared empty array instance used for empty instances.
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};//如果故意在有参构造函数中传0用的数组.
    
        /**
         * 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.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//如果用空参构造函数,把这个作为空参构造函数的值.
    
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access//ArrayList的底层函数
    
        /**
         * The size of the ArrayList (the number of elements it contains).
         *ArrayList的长度
         * @serial
         */
        private int size;
    

    java集合-ArrayList中EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别

    缩减函数

    /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    //缩减这个ArrayList实例的容量道它的最优大小.一个程序可以用这个操作去缩小一个ArrayList实例的大小.
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            //如果size为0就给他变回空数组,如果不是空数组,则复制它的数组,截掉那部分值为0的尾部
            1,2,3,4,0,0,0->1,2,3,4
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
    

    计算最小扩容长度函数(预备)

    /**
     * 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//最小需求容量
     */
    //增加这个ArrayList的容量,如果是不必要的,保证这个ArrayList中的元素数量是最小容量.
    //在扩容的时候使用的函数,保证扩容增长的长度是最小的长度.
    public void ensureCapacity(int minCapacity) {
        //最小扩展量
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            //如果不是固定的那个空数组,最小的扩展就是0
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            //如果传入的数比默认的空数组大,也被建议先采用默认值.
            : DEFAULT_CAPACITY;
    //如果最小需求容量大于最小扩展值(最小扩展值实际上只在空ArrayList被第一次加入元素的时候才有用处[用处是直接把容量从0加到10],其他时候没用)
        if (minCapacity > minExpand) {
            //确定准确的ArrayList容量是什么.
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    如上函数被addAll()用到的
    
    
    
    如下函数是被add()用到的
    
    
    //确定这个最小容量的大小.
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果elementData还是最小初始数组   
        <---2.被 ensureCapacityInternal调用,返回值给ensureExplicitCapacity
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           //看看这个最小容量大还是规定的默认长度(10)大,返回大的那个
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //如果
    private void ensureCapacityInternal(int minCapacity) {  <---1.被add()方法调用
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    //确定准确的ArrayList容量是什么.
    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
     */
    //增加容量去确保ArrayList拥有至少由最小容量给定的容量.
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//老板长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);//old+old/2
        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);
    }
    

    增加函数

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    

    上面三个函数要一同看

    当调用一个add()方法的时候首先运行的是 ensureCapacityInternal(size + 1);方法,判断ArrayList是否需要扩容.在这个函数中会先调用private static int calculateCapacity(Object[] elementData, int minCapacity) 函数,去对增加的数量进行判断.

    对于用空参构造函数构造的ArrayList,为了节省空间实际上使用的是一个空数组,并没长度(并不是10).所以在这个函数中要判断增加的元素数量是否大于10(也就是ArrayList的默认长度),如果不大于10,用10作为容量,反之用传入的那个长度作为容量.

    然后调用private void ensureExplicitCapacity(int minCapacity)在这里又进行了另一次判断,这里要注意传入的容量(minCapacity)未必大于ArrayList的现有长度.,所以在这里要有一次判断判断是否应该要调用grow()方法进行扩大.

    一个toArray()方法

    public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of a's runtime type, but my contents:
                //用传入的值去更改这个ArrayList中的所有内容
                //Arrays.copyOf复制指定的数组,用空值截断或填充(如有必要),以便复制具有指定的长度。
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;//如果进来的数组长度大于ArrayList长度,则将数组中最后一个设置为空.
            return a;//返回数组
        }
    

    一个get()方法

    public E get(int index) {
        rangeCheck(index);
    
        return elementData(index);
    }
      E elementData(int index) {
            return (E) elementData[index];
        }//返回index位置上的索引的值
    

    get()方法是由两个函数组成.目标是更加的简洁

    set()方法也是大体如此

    public E set(int index, E element) {
        rangeCheck(index);//确定没有超出边界
    
        E oldValue = elementData(index);//保留原先的值
        elementData[index] = element;//覆盖新的值
        return oldValue;//将原先的值返回
    }
    

    增加函数

    public void add(int index, E element) {
        rangeCheckForAdd(index);
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!//判断是否需要扩容,上面讲过了.
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//从index位置上的元素开始将后面的所有元素(总长度size-索引位置index)挪到index+1的位置上
        elementData[index] = element;//把值放在index索引处
        size++;//长度计数器增加.
    }
    

    减小函数

    public E remove(int index) {
        rangeCheck(index);
    
        modCount++;
        E oldValue = elementData(index);//取出原先的值
    
        int numMoved = size - index - 1;//因为index上的值消失,所以实际上是从index+1开始的,也正是因此要多减一个
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//从index+1开始复制,一直到数组解为,粘贴到index开始的位置上
        //此时会有两个最后一个元素,让他为空
        elementData[--size] = null; // clear to let GC do its work
        //如果一个对象所有的引用都为空,则与GC-root断链,会被JVM标记,然后清除.
        return oldValue;
    }
    

    add All()

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//将传入的集合化为数组,每个集合中的元素都会被擦除成Object类型.
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);//从a(传入数组)的0位置复制该数组长度,从size开始粘贴到ArrayList维护的底层数组.
        size += numNew;
        return numNew != 0;
    }
    

    这个本质来讲没啥区别,但是重点是用到了ensureCapacityInternal(size + numNew); 这个方法,在上面的模块中,这个一直没用用到.请回到上面去看一下.注意minCapacity为最小需求容量.上面可能有些出入,请记住这个意思.

    如上就是我们经常用到的ArrayList方法的详解.

    还有一部分方法,大多原理和如上几个方法重复,或者平时基本不用.就不再写出来了.

  • 相关阅读:
    mysql 查询优化
    图解Java常用数据结构(一)
    mybatis 详解(五)------动态SQL
    Mybatis的ResultMap的使用
    java系统变慢的优化简略步骤
    mysql基本操作
    mysql数据库的优化 一
    tomcat的启动启动与关闭
    vueJs的简单入门以及基础语法
    Oracle常用命令-用户、表空间、赋权限、导入导出
  • 原文地址:https://www.cnblogs.com/yanzezhong/p/12895889.html
Copyright © 2011-2022 走看看