• JDK集合框架--ArrayList


    ArrayList,从类名就可以看出来,这是由数组实现的List,即内部是用数组保存元素的有序集合。先看看主要的成员变量,比较简单:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        /**
         * 默认的初始数组大小
         */
        private static final int DEFAULT_CAPACITY = 10;/**
         *用于储存具体元素的数组对象
         */
        transient Object[] elementData; // non-private to simplify nested class access
        /**
         *
         *数组中元素的数量
         */
        private int size;

    三个构造函数:

    /**
         *
         * 创建一个确定初始大小的数组
         * @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);
            }
        }
    
        /**
         * 不创建数组,使用默认的空数组
         */
       private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         *传入一个Collection 的实现类 并转换成Object[]数组*/
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)->有些集合的toArray方法返回的对象类型可能不是Object[].class
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // 使用空数组作为初始数组
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

    如果可以大体上确定ArrayList需要所元素的数量,尽量使用ArrayList(int initialCapacity)指定初始容量,可以有效避免其扩容次数(多数底层使用数组实现的集合有相同的特点)

    接下来分析其扩容机制,先看看add(E e)方法:    

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  //判断是否需要扩容
            elementData[size++] = e;//添加对象引用
            return true;
        }
    
    private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
       
        
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            //即使用的是无参构造器创建对象,且是第一次添加元素的情况
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             //如果当前所需最小容量小于默认值10,则最小值作为当前容量
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
            }
        
        
      private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            //如果当前所需的容量超出,之前数组大小 则进行扩容操作
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        //核心扩容方法
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);//原来数组的1.5倍
            //新数组的大小必须至少为原数组大小的1.5倍,否则直接扩容为1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //新的数组长度如大于默认最大的数组长度MAX_ARRAY_SIZE
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 创建数组,将旧数组中的数据复制到新数组中
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
       private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
           //所需最小的数组大小如果大于MAX_ARRAY_SIZE,则直接取int类型最大值,反之,取默认最大值MAX_ARRAY_SIZE
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    所有的添加操作都是需要调用ensureCapacityInternal方法,判断是否需要,并进行扩容操作

    这里总结一下:

      ArrayList默认容量是10,如果初始化时一开始指定了容量,或者通过集合作为元素,则容量为指定的大小或参数集合的大小。每次扩容为原来的1.5倍,如果新增后超过这个容量,则容量为新增后所需的最小容量。如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量与限制的容量进行判断,超过则指定为Integer的最大值,否则指定为限制容量大小。然后通过数组的复制将原数据复制到一个更大(新的容量大小)的数组。

    再看看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;
        }
       //根据具体的对象移除
        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;
        }
            //移除所有元素
            public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }

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

    所有的移除工作都是找到对应的数组下标项,然后置空,具体的对象回收是jvm的工作;进行删除和插入操作时,为了维持数组中元素的连续性,则需要调用System.arraycopy(),重新并排数组元素,时间复杂度是线性的,效率很低,所以ArrayList不适合需要经常对集合进行动态操作的场景。

      

    最后看看ArrayList访问元素的方法:

        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }

    很简单,就是利用数组的索引访问,常数时间复杂度,效率很高,所以对于频繁读取的场景,使用ArrayList再好不过了

        

      

  • 相关阅读:
    【读书笔记】【深入理解ES6】#13-用模块封装代码
    【读书笔记】【深入理解ES6】#12-代理(Proxy)和反射(Reflection)API
    【读书笔记】【深入理解ES6】#11-Promise与异步编程
    【读书笔记】【深入理解ES6】#10-改进的数组功能
    【读书笔记】【深入理解ES6】#9-JavaScript中的类
    【读书笔记】【深入理解ES6】#7-Set集合和Map集合
    【读书笔记】【深入理解ES6】#6-Symbol和Symbol属性
    关于maven打包文件不全的处理方式
    关于Dubbo的常用模型
    Dubbo源码本地运行demo遇到的问题
  • 原文地址:https://www.cnblogs.com/qzlcl/p/10902934.html
Copyright © 2020-2023  润新知