JDK源码阅读(三):ArrayList源码解析

温馨提示

如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。

关注公众号 回复关键字领取免费学习资源

- 电子书 领取《Java并发》《Java编程思想等》

- SpringCloud 领取SpringCloud全套视频学习资源

- SpringBoot   领取SpringBoot 全套视频学习资源

正文共:4212字23图  预计阅读时间:11分钟 

今天来看一下 ArrayList 的源码

  • 目录

    • 介绍

    • 继承结构

    • 属性

    • 构造方法

    • add 方法

    • remove 方法

    • 修改方法

    • 获取元素

    • size() 方法

    • isEmpty 方法

    • clear 方法

    • 循环数组

1. 介绍

一般来讲文章开始应该先介绍一下说下简介。这里就不介绍了 如果你不知道 ArrayList 是什么的话就没必要在看了。大致讲一下一些常用的方法

2. 继承结构

ArrayList 源码定义:

640?wx_fmt=png

ArrayList 继承结构如下:

640?wx_fmt=png

  • Serializable 序列化接口

  • Cloneable 前面我们在看 Object 源码中有提到这个类,主要表示可以进行克隆

  • List 主要定义了一些方法实现

  • RandomAccess 也是一个标记类,实现 RandomAccess 表示该类支持快速随机访问。

3. 属性

ArrayList 中的主要属性方法有:

初始化容量,默认为 10

private static final int DEFAULT_CAPACITY = 10;

空数组

private static final Object[] EMPTY_ELEMENTDATA = {};

也是一个空数组,和上面的数组相比主要的作用是在添加元素的时候知道数组膨胀了多少。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList 的大小

private int size;

注意 ArrrayList 中有一个 modCount 成员变量,来记录修改次数,主要是在使用迭代器遍历的时候,用来检查列表中的元素是否发生结构性变化(列表元素数量发生改变)了,主要在多线程环境下需要使用,防止一个线程正在迭代遍历,另一个线程修改了这个列表的结构。好好认识下这个异常:ConcurrentModificationException。对了,ArrayList 是非线程安全的。

4. 构造方法

我们来看一下 ArrayList 的构造方法

640?wx_fmt=png

可以看出无参构造会直接创建一个指定 DEFAULTCAPACITYEMPTYELEMENTDATA 的数组,而 DEFAULTCAPACITYEMPTYELEMENTDATA 数组默认是空的。

所以在执行一下代码的时候 ArrayList 默认创建的是一个初始化容量为 0 的数组。

ArrayList list = new ArrayList();

有参构造方法:

640?wx_fmt=png

传入创建数组的大小,如果大于 0 就创建一个传入参数大小的数组,如果等于 0 就就指定为空数组。如果小于 0 就会抛异常。

640?wx_fmt=png

看源码我们可以看到传入 Collection 的构造方法做的事情就是复制数组,将已有的集合复制到新的集合中。

5.add 方法

ArrayList 底层是用数组来实现的,那我们就一起来看以下 Add 方法是如何实现的

以下是 Add 方法的实现源码。640?wx_fmt=png

可以看到 ArrayList 在添加元素之前先检查一下集合的大小

640?wx_fmt=png

在 ensureExplicitCapacity 方法中,首先对修改次数 modCount 加一,这里的 modCount 给 ArrayList 的迭代器使用的,在并发操作被修改时,提供快速失败行为(保证 modCount 在迭代期间不变,否则抛出 ConcurrentModificationException 异常,可以查看源码 865 行),接着判断 minCapacity 是否大于当前 ArrayList 内部数组长度,大于的话调用 grow 方法对内部数组 elementData 扩容,grow 方法代码如下:640?wx_fmt=png

6.remove 方法

ArrayList 的删除操作一共有两种一种是根据索引删除,一种是根据内容删除。

根据索引删除元素

640?wx_fmt=png640?wx_fmt=png

我们可以看到首先判断一下是否为空。不为空的话就开始循环查找元素,用 equals 来判断元素是否相同,如果一致就调用 fastRemove 来删除元素。然后通过 System.arraycopy 进行自身复制。

7. 修改方法

640?wx_fmt=png

8. 获取元素

因为本身 ArrayList 就是用数组来实现的,所以获取元素就相对的来说简单一点。

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

9.size() 方法

640?wx_fmt=png

9.isEmpty 方法

640?wx_fmt=png

直接返回 size==0 的结果,是不是非常简单。

10.clear 方法

640?wx_fmt=png

11. 循环数组

for 循环

for 循环可能在 java 中是最常用的遍历方法主要实现:

640?wx_fmt=png

迭代器 iterator

先看实现:

640?wx_fmt=png

640?wx_fmt=png

 /**	
     * An optimized version of AbstractList.Itr	
     */	
    private class Itr implements Iterator<E> {	
        //游标, 下一个要返回的元素的索引	
        int cursor; 	
        // 返回最后一个元素的索引; 如果没有这样的话返回-1.	
        int lastRet = -1; 	
        int expectedModCount = modCount;	
	
        Itr() {}	
        //通过 cursor != size 判断是否还有下一个元素	
        public boolean hasNext() {	
            return cursor != size;	
        }	
	
        @SuppressWarnings("unchecked")	
        public E next() {	
        //迭代器进行元素迭代时同时进行增加和删除操作,会抛出异常	
            checkForComodification();	
            int i = cursor;	
            if (i >= size)	
                throw new NoSuchElementException();	
            Object[] elementData = ArrayList.this.elementData;	
            if (i >= elementData.length)	
                throw new ConcurrentModificationException();	
                //游标向后移动一位	
            cursor = i + 1;	
            //返回索引为i处的元素,并将 lastRet赋值为i	
            return (E) elementData[lastRet = i];	
        }	
	
        public void remove() {	
            if (lastRet < 0)	
                throw new IllegalStateException();	
            checkForComodification();	
	
            try {	
            //调用ArrayList的remove方法删除元素	
                ArrayList.this.remove(lastRet);	
                //游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了	
                cursor = lastRet;	
                //lastRet恢复默认值-1	
                lastRet = -1;	
                //expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1	
                expectedModCount = modCount;	
            } catch (IndexOutOfBoundsException ex) {	
                throw new ConcurrentModificationException();	
            }	
        }	
	
        @Override	
        @SuppressWarnings("unchecked")	
        public void forEachRemaining(Consumer<? super E> consumer) {	
            Objects.requireNonNull(consumer);	
            final int size = ArrayList.this.size;	
            int i = cursor;	
            if (i >= size) {	
                return;	
            }	
            final Object[] elementData = ArrayList.this.elementData;	
            if (i >= elementData.length) {	
                throw new ConcurrentModificationException();	
            }	
            while (i != size && modCount == expectedModCount) {	
                consumer.accept((E) elementData[i++]);	
            }	
            // update once at end of iteration to reduce heap write traffic	
            cursor = i;	
            lastRet = i - 1;	
            checkForComodification();	
        }	
        //前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的	
        final void checkForComodification() {	
            if (modCount != expectedModCount)	
            //也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常	
                throw new ConcurrentModificationException();	
        }	
    }

从上面的代码我们得出,在遍历的时候如果删除或者新增元素都会抛异常出来,而修改不会。看下方例子。640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

原文地址:https://www.cnblogs.com/felordcn/p/12142515.html