• ArrayList源码解读


    1.背景

    源码解读是提升编程能力的有效方式

    在面试中也经常问到.....

    2.自己开发一个类似ArrayList的对象

    解读源码的最佳方式就是自己开发一个类似的....

    package com.ldp.collection.my;
    
    import java.util.AbstractList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.RandomAccess;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 04/04 6:39
     * @description <p>
     * 需求:自己设计一个基于数组的集合,功能类似ArrayList
     * 设计思路
     * 1.使用数组来存放添加的元素
     * 2.默认数组长度为10,满了后按照原来的1.5倍扩容
     * 3.设置的最大数组长度为MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,实际最大长度不超过 Integer.MAX_VALUE
     * 4.编写一个构造方法,一个添加元素的方法,一个获取元素的方法
     * 难点:
     * 扩容算法,即计算的实际的数组长度
     * 总结:
     * 当你看懂了我自己写的MyArrayList这个集合类,你再去看ArrayList,
     * 你会发现是不是ArrayList是不是"抄袭"我的MyArrayList,居然和我写的一模一样...哈哈哈...
     * </p>
     */
    public class MyArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        /**
         * 默认的空集合
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        /**
         * 用于存放数据的集合
         * transient:临时的
         * 本文要介绍的是Java中的transient关键字,transient是短暂的意思。
         * 对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。
         * 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。
         */
        transient Object[] elementData;
        // 数组的默认长度
        private static final int DEFAULT_CAPACITY = 10;
        /*最大数组长度
         *有些虚拟机会预留出一定长度作为数组的一些头信息,所以数组最大长度达不到Integer的最大值2~32-1 = 2147483647(21亿..)左右,
         *这个-8是为了避免内存溢出。
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        // 数组长度
        private int size;
    
        /**
         * 构造方法
         */
        public MyArrayList() {
            // 设一个默认的空数组,Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         * 添加一个元素
         *
         * @param e
         * @return
         */
        @Override
        public boolean add(E e) {
            // 确保数组长度足够,需要的最小长度为当前数组的长度+1
            ensureCapacityInternal(size + 1);
            elementData[size++] = e;
            return true;
        }
    
        /**
         * 确保数组的容量不出界
         *
         * @param minCapacity
         */
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        /**
         * 计算数组的长度(容量)
         *
         * @param elementData 集合的数组
         * @param minCapacity 需要的长度
         * @return 返回一个数组长度
         */
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            // 如果数组为默认的空数组
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                // 在默认的数组长度10 与 需要的长度中取最大值
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    
        /**
         * modCount 解读
         * modCount只记录arraylist被修改次数,在每次添加修改删除的时候进行+1,
         * 这个参数的作用是在多线程并发的时候,由于arraylist不是线程安全的,在当前线程进行遍历或其他操作的时候,
         * 如果其它线程修改了改值,那么当前线程能检测到该参数发生变化,会抛异常。
         *
         * @param minCapacity
         */
        private void ensureExplicitCapacity(int minCapacity) {
            // 累加集合的修改次数
            modCount++;
            // 如果需要的数组长度大于了实际的数组长度,则进行扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        /**
         * 扩容算法
         *
         * @param minCapacity
         */
        private void grow(int minCapacity) {
            // 原来的数组长度
            int oldCapacity = elementData.length;
            /* 新的数组长度,
             * 1.oldCapacity >> 1 右移动2位表示除2,即新长度为原来的+原来的一半(若原来为10,变为15)
             * 2.oldCapacity 第一次进入的时候 oldCapacity =0,因此计算出来的newCapacity=0
             */
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            // 加一半后长度还是不够,则用传递过来的数值(addAll来说,就是原长度+新集合长度)
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 新的数组长度大于了设置的最大长度时,计算一个新的长度出来
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 执行扩容,扩容的实现时把原来的数组复制到新的数组中来,新的数组长度就是newCapacity
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        /**
         * 数组最大值计算
         *
         * @param minCapacity 数组的当前需要的最小长度
         */
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0)
                throw new OutOfMemoryError();
            // 三目运算 , minCapacity大于当前设置的数组最大值时,就取Integer.MAX_VALUE,否则就是设置的最大值
            // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
            return (minCapacity > MAX_ARRAY_SIZE) ?
                    Integer.MAX_VALUE :
                    MAX_ARRAY_SIZE;
        }
    
        @Override
        public E get(int index) {
            // 检查下标是否越界
            rangeCheck(index);
            return elementData(index);
        }
    
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
        private String outOfBoundsMsg(int index) {
            return "Index: " + index + ", Size: " + size();
        }
    
        @SuppressWarnings("unchecked")
        E elementData(int index) {
            // 从数组中取出一个元素
            return (E) elementData[index];
        }
    
        @Override
        public int size() {
            return size;
        }
    }
    View Code

    3.源码解读

    3.1.构造方法解读

    public ArrayList() {
    // 设一个默认的空数组,Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 开始new ArrayList()的时候,数组长度其实是0,并不是10,是个空数组。
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    3.2.add方法解读

    @Override
    public boolean add(E e) {
    // 确保数组长度足够,需要的最小长度为当前数组的长度+1
    ensureCapacityInternal(size + 1);
    // 放在当前元素的下一个位置
    elementData[size++] = e;
    return true;
    }
    确定数组长度的方法ensureCapacityInternal
    private void ensureCapacityInternal(int minCapacity) {
    // calculateCapacity 计算需要数组长度minCapacity
    // ensureExplicitCapacity 确定明确的值,即是否扩容
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    calculateCapacity 计算需要数组长度minCapacity

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果数组为默认的空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 在默认的数组长度10 与 需要的长度中取最大值
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }
    ensureExplicitCapacity 确定明确的值,即是否扩容
    
    
    /**
    * modCount 解读
    * modCount只记录arraylist被修改次数,在每次添加修改删除的时候进行+1,
    * 这个参数的作用是在多线程并发的时候,由于arraylist不是线程安全的,在当前线程进行遍历或其他操作的时候,
    * 如果其它线程修改了改值,那么当前线程能检测到该参数发生变化,会抛异常。
    *
    * @param minCapacity
    */
    private void ensureExplicitCapacity(int minCapacity) {
    // 累加集合的修改次数
    modCount++;
    // 如果需要的数组长度大于了实际的数组长度,则进行扩容
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }

    grow扩容算法

    /**
    * 扩容算法
    *
    * @param minCapacity
    */
    private void grow(int minCapacity) {
    // 原来的数组长度
    int oldCapacity = elementData.length;
    /* 新的数组长度,
    * 1.oldCapacity >> 1 右移动2位表示除2,即新长度为原来的+原来的一半(若原来为10,变为15)
    * 2.oldCapacity 第一次进入的时候 oldCapacity =0,因此计算出来的newCapacity=0
    */
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 加一半后长度还是不够,则用传递过来的数值(addAll来说,就是原长度+新集合长度)
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    // 新的数组长度大于了设置的最大长度时,计算一个新的长度出来
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // 执行扩容,扩容的实现时把原来的数组复制到新的数组中来,新的数组长度就是newCapacity
    elementData = Arrays.copyOf(elementData, newCapacity);
    }

    最大长度计算

    /**
    * 数组最大值计算
    *
    * @param minCapacity 数组的当前需要的最小长度
    */
    private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
    throw new OutOfMemoryError();
    // 三目运算 , minCapacity大于当前设置的数组最大值时,就取Integer.MAX_VALUE,否则就是设置的最大值
    // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
    }

    3.3get方法解读

    这个比较简单

    @Override
    public E get(int index) {
    // 检查下标是否越界
    rangeCheck(index);
    // 获取一个元素
    return elementData(index);
    }

    /**
    * 检查下标是否越界
    * @param index
    */
    private void rangeCheck(int index) {
    if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private String outOfBoundsMsg(int index) {
    return "Index: " + index + ", Size: " + size();
    }

    @SuppressWarnings("unchecked")
    E elementData(int index) {
    // 从数组中取出一个元素
    return (E) elementData[index];
    }

    3.4.size方法解读

    @Override
    public int size() {
    return size;
    }

    4.总结

    新增一个元素,调用add方法,
    如果长度没超出,那么就在数组下个位置放置该元素,
    如果超出了,按照当前长度的一半增长,采用的是右位移的算法(如果对位移等基础知识不清楚的可以看之前讲的<<编程基础中的基础>>)。

    最后:其实你采用断点的方式debug一下,或许对源码的理解会更加深入.

    完美!

  • 相关阅读:
    边走边学Nodejs (基础入门篇)
    Android应用打包安装过程具体解释
    ubuntu与centos安装软件的不同点总结
    你好,C++(12)怎样管理多个类型同样性质同样的数据?3.6 数组
    oracle暂时表空间 ORA-01652:无法通过16(在表空间XXX中)扩展 temp 字段
    iOS中sqlite3操作
    sparkSQL1.1入门之二:sparkSQL执行架构
    [NHibernate]视图处理
    [NHibernate]立即加载
    [NHibernate]延迟加载
  • 原文地址:https://www.cnblogs.com/newAndHui/p/16101626.html
Copyright © 2020-2023  润新知