• 第二章 ArrayList源码解析


    1、ArrayList的创建(构造器)

      常见的两种方式:

    List<String> strList = new ArrayList<String>();
    List<String> strList2 = new ArrayList<String>(2);
    View Code

      基本属性:

        //对象数组:ArrayList的底层数据结构
        private transient Object[] elementData;
        //elementData中已存放的元素的个数,注意:不是elementData的容量
        private int size;
    View Code
    • ArrayList实现了java.io.Serializable,即采用Java默认的序列化机制。
    • elementData(Object数组)是ArrayList的底层实现,使用transient关键字修饰,说明该属性不采用Java默认的序列化机制,ArrayList自己实现了序列化和反序列化的方法,见下面代码。
    /**
         * Save the state of the <tt>ArrayList</tt> instance to a stream (that
         * is, serialize it).
         *
         * @serialData The length of the array backing the <tt>ArrayList</tt>
         *             instance is emitted (int), followed by all of its elements
         *             (each an <tt>Object</tt>) in the proper order.
         */
        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();
            }
        }
    
        /**
         * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
         * deserialize it).
         */
        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();
                }
            }
        }
    View Code

      从上边源码可以看出,先调用java.io.ObjectOutptuStream的defaultWriterObject方法,进行默认的序列化操作,用transient修饰的字段没有被序列化。

    • 好处:ArrayList会开辟多余的空间来保存数据,序列化和反序列化这些没有存放数据的空间会消耗更多资源,所以ArrayList的数组用transient修饰,告诉虚拟机不采用默认的序列化机制,使用自己重写的序列化和反序列化方法,仅仅序列化已经存放的数据。

      构造器:

       /**
         * 创建一个容量为initialCapacity的空的(size==0)对象数组
         */
        public ArrayList(int initialCapacity) {
            super();//即父类protected AbstractList() {}
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
    
        /**
         * 默认初始化一个容量为10的对象数组
         */
        public ArrayList() {
            this(10);//即上边的public ArrayList(int initialCapacity){}构造器
        }
    View Code
    • 上边有参构造器中super()调用的ArrayList的父类AbstractList的构造器:
    protected AbstractList() {
    
    }

      实际应用中,如果我们能判断出所需的ArrayList的大小,有两个好处:

    • 节省内存空间,如果我们只需要存储两个对象到容器中,就 new ArrayList(2);

    避免扩容引起的性能消耗(扩容下面会讲到)。

    2、get()和set()方法

      get(int index)源码:

        /**
         * 按照索引查询对象E
         */
        public E get(int index) {
            RangeCheck(index);//检查索引范围
            return (E) elementData[index];//返回元素,并将Object转型为E
        }
    View Code

      set(int index, E element)源码:

        /**
         * 更换特定位置index上的元素为element,返回该位置上的旧值
         */
        public E set(int index, E element) {
            RangeCheck(index);//检查索引范围
            E oldValue = (E) elementData[index];//旧值
            elementData[index] = element;//该位置替换为新值
            return oldValue;//返回旧值
        }
    View Code
        /**
         * 检查索引index是否超出size-1
         */
        private void RangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
        }
    View Code
    • get()和set()方法都对索引index进行了检查(调用RangeCheck()方法),是为了详细报错信息并且缩小检查范围(index < 0 || index >= size,size是数组已存放数据数量),对于数组而言,如果index不满足要求(index < 0 || index > length,length是数组容量),会抛出下标越界异常,但如果length是10,size = 2,get(9)会返回null,这也是为什么缩小检查范围的原因;
    • get()和set()方法都可以在常数时间内完成。

    3、add()和addAll()方法

      add(E element)和add(int index, E element)源码:

        /**
         * 紧接着数组中最后一个数据后插入
         *
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // 确保数组有足够容量,不够扩容
            elementData[size++] = e;
            return true;
        }
    View Code
        /**
         * index索引处插入数据
         * 
         */
        public void add(int index, E element) {
            rangeCheckForAdd(index);   //检查索引是否满足条件
    
            ensureCapacityInternal(size + 1);  // 确保数组容量够用,不够扩容
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);    //将index及index后的元素后移,空出index位置
            elementData[index] = element;
            size++;
        }
    View Code
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);    //原来的1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;    
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);    
            elementData = Arrays.copyOf(elementData, newCapacity);    //扩展空间并复制
        }
    View Code
    • 从源码看,插入数据时,都会先先检查是否需要扩容,如果需要通过grow()方法完成。

      ArrayList自动扩容过程:

    • new一个容量为new_capacity的对象数组;
    • 再将old_elementData数组里数据复制到新数组elementData中。

      空间问题解决,开始插入数据:

    • add(E element)方法是在最后插入(index = size处)
    • add(int index, E element)需要先将index及index后的元素后移,让出index位置,再将数据插入到index位置,该方法有着线性的时间复杂度。

  • 相关阅读:
    【数学】BSGS算法
    区块链【2】我们为什么要给比特币记账?
    区块链【1】必须要说的比特币
    项目管理【08】 | 项目整体管理-实施整体变更控制
    项目管理【07】 | 项目整体管理-监控项目工作
    项目管理【06】 | 项目整体管理-指导和管理项目执行
    项目管理【05】 | 项目整体管理-制定项目管理计划
    项目管理【04】 | 项目整体管理-制定项目章程
    项目管理【03】 | 项目管理基础-项目管理5大过程组与10大知识领域
    项目管理【02】 | 项目管理基础-信息系统项目的生命周期模型
  • 原文地址:https://www.cnblogs.com/lmmblogs/p/8604900.html
Copyright © 2020-2023  润新知