源码学习day01(ArrayList)


根据学习计划,我们现阶段已将开始学习源码,因为我们数据专业已经学过了List的数据结构相关的一些知识,所以我就从ArrayList开始入手,下面是我在看Array源码时遇到的一些问题。

首先为啥要实现那个么多接口呢

在这里插入图片描述
我们可以看到,ArrayList除了实现了基本的List接口,还额外的实现了RandomAccess,Cloneable,Serializable, 以及继承了AbstractList抽象类
所以,我就在想为什么要多实现以及继承这几个类或者接口呢?
我们追进去List接口可以看到,里面都是些集合的基本操作方法,
在这里插入图片描述
那么AbstractList类
在这里插入图片描述
里面也是对与集合的基本操作,这就有导致我出现了一个新的问题,为什么要同时继承和实现AbstractList抽象类和List接口呢?经过不断地百度,我觉得我找到了一个我认为合理的解释,ArrayList实现List接口,是因为他要有List必有得一些特征属性,而继承AbstractList抽象类,因为有些方法没有必要去重写那就像size(),所以像这类实现方法是通用的,就可以在一个抽象类中统一实现,然后将add()等自由化程度较高地方法交给每个实现类自己去实现
那么randomAccess得作用又是什么呢?
在这里插入图片描述
追进去后发现竟然什么都没有,离谱啊,官方解释是只要List实现这个接口,就能支持快速随机访问,那么什么时快速随机访问呢,这就要进行一下比较了,
我们可以看一下LinkedList的源码
在这里插入图片描述
可以看到LinkedList并没有去实现randomAccess接口,然后我们经过一些测试就会发现LinkedList的迭代相比于for循环要快,而ArrayList的for循环则要比迭代快,实现RandomAccess接口的List可以通过for循环来遍历数据比使用iterator遍历数据更高效,未实现RandomAccess接口的List可以通过iterator遍历数据比使用for循环来遍历数据更高效。
那么cloneable的作用呢?
只有实现了这个接口才能调用Object.clone()方法,来对ArrayList进行克隆,不然就会抛出CloneNotSupportedException。
至于Serializable接口,只要想将对象进行序列化就必须要实现这个接口

接下来就要开始源码了

首先就是相关的属性

    //序列化的序号,用来判断序列化与反序列化方式是否正确
    private static final long serialVersionUID = 8683452581122892189L;
    //默认数组的大小设置
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 大小为0的数组,当调用有参构造创建初始化容量为0的ArrayList时赋值给elementData
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 大小为0的数组,当调用无参构造创建ArrayList时赋值给elementData
     * 该数组区别于EMPTY_ELEMENTDATA是为了在第一次添加元素时判断如何扩容
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

 	//数组
    transient Object[] elementData; 

   //元素的多少
    private int size;

我们可以看到这里面有两个空数组,EMPTY_ELEMENTDATA 的空数组,是在调用有参构造器,当输入参数为零的时候调用的,默认参数大小就是0,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA是在无参构造器中调用的,并且在添加元素是的默认大小为10

 //初始化数组对象
    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);
        }
    }
    //实例化对象, 默认空数组 
    //默认初始化容量为10,但是在添加元素之前容量一直为0
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
      
    //实例化对象,可添加集合参数
    public ArrayList(Collection<? extends E> c) {
        //首先将集合转化为数组
        Object[] a = c.toArray();
        //判断数组的大小 如果数组的大小为0的话,直接赋值空数组
        if ((size = a.length) != 0) {
            //c.toArray()返回的类型可能不是Object类型
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 传入的集合为空集合
            elementData = EMPTY_ELEMENTDATA;
        }
    }

这是一个无参构造器和两个有参的构造器

如何扩容以及看这段代码是遇到的问题

这是添加元素的方法(感觉可以更直观的理解扩容)

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

这是扩容相关的代码

 //对数组进行扩容
    public void ensureCapacity(int minCapacity) {
        //最小的扩展策略 
        //如果元素数组不为默认的空,则 minExpand 的值为0,反之值为10
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;
        // 如果最小容量大于已有的最大容量
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    /**
   * 计算最小扩容量(被调用)
       */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果元素数组为默认的空
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 获取“默认的容量”和“传入参数 minCapacity ”两者之间的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    /**
   * 得到最小扩容量
   */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    

    /**
     * 判断是否需要扩容
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 如果最小容量比数组的长度还大
        if (minCapacity - elementData.length > 0)
        // 就调用grow方法进行扩容
            grow(minCapacity);
    }
    //要分配的最大数组大小
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // 扩容的核心方法
    private void grow(int minCapacity) {
        //将当前元素的数组长度定义为 oldCapacity 
        int oldCapacity = elementData.length;
        //定义扩容后的数组长度 为老数组长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后继续检查新容量是否大于最小容量, 若还小于就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
             // 再检查新容量是否超出了ArrayList 所定义的最大容量
             // 若超出,则调用hugeCapacity()
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    /**
    *比较minCapacity 和 MAX_ARRAY_SIZE的大小
    */
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

首先如果使用的是无参构造器,调用添加元素的方法,会进入到ensureCapacityInternal()方法中去,size默认初始值为0,传进去的参数是1,
在这里插入图片描述
接着进calculateCapacity()方法,
在这里插入图片描述
注意这时候我们用的是无参构造器创建的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会进入if中,返回值就是10,然后进入ensureExplicitCapacity()方法
在这里插入图片描述
这里我们看到一个modCount,然后我有多想了,这玩意儿干啥用的,一会解释,先说正事。
minCapacity 的值是10,elementData数组的长度也是10,所以不用扩容,那要是再添加元素呢?size值会自增,也就是minCapacity 的值,直到size值大于
elementData的长度,就会到扩容的核心

 private void grow(int minCapacity) {
        //将当前元素的数组长度定义为 oldCapacity 
        int oldCapacity = elementData.length;
        //定义扩容后的数组长度 为老数组长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后继续检查新容量是否大于最小容量, 若还小于就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
             // 再检查新容量是否超出了ArrayList 所定义的最大容量
             // 若超出,则调用hugeCapacity()
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

如此反复,这里就又有了一个问题,为什么扩容的倍数是1.5,而不是1.2,不是2呢,百度到的都是一些很笼统的答案, 因为 1.2 未免扩容空间太小 , 而 2 倍则扩充的空间太大。这个问题先保留。
如果是有参构造器呢,那就直接从第一个元素就开始扩容,前4次的扩容都是每次多扩容1个长度,因为毕竟有个小数

最后之前的保留问题modCount的作用是什么呢

经过观察我们会发现,再涉及到对列表结构修改时,modCount就会自增,然后我们接着寻找
在这里插入图片描述
在这里插入图片描述
我们可以看到再涉及到列表的保存以及迭代时都提到了modCount,并且都有一个变量来引用modCount在方法中也会对modCount和expectedModCount
进行判断,如果不相等就会抛出ConcurrentModificationException()异常,
由此,我们不难推断出,当ArrayList进行迭代或者保存时,如果另外有一个操作去修改list集合,就会抛出ConcurrentModificationException(),避免一下不必要的意外。
以上就是在目前能力之下对所看ArrayList部分源码的理解,可能不对,还望指正。

配个图吧
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值