ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解。

ArrayList源码解析

ArrayList中的变量

在这里插入图片描述
通过上图可以看到,ArrayList中总共有7个变量,下面看下每个变量的作用:

    /**
     * 序列化
     */
    private static final long serialVersionUID = 8683452581122892189L;
    /**
     * 默认的初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 用于在构造函数中,初始化一个空的数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 用于无参构造函数中,给一个空的数组
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * 这个是真正存储元素的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access
    /**
     * size,是用来记录arrayList中,即elementData里的元素的大小,数组中共有多少个元素
     * @serial
     */
    private int size;
    /**
     * 这个参数,是数组所允许的最大长度,要是超出了,可能会报OutOfMemoryError异常
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList构造函数

ArrayList中有三个构造函数:
在这里插入图片描述

    /**
     * 指定一个具有初始容量的空数组,可以自己指定容量的大小
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException 指定的初始容量值若是负值,会抛出这个异常
     */
    public ArrayList(int initialCapacity) {
        //容量大于0,就把elementData初始化一个具有initialCapacity大小的数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        //要是指定的容量值是0,就初始化一个空数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        //指定的容量值小于0.抛出不合法参数异常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 无参构造函数,会指定一个初始容量为10的数组
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
     *
     * @param c 传入的集合,会把集合中的元素放入数组中
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //数组中元素的长度不为0,就把c的元素copy到elementData中
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //要是元素个数是0个,就初始化一个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList中的add方法

    /**
     * add方法,会把一个元素添加到数组的末尾
     *
     * @param e 传入的元素
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //size是目前数组存的元素的个数,传入size+1,即需要的数组长度
        //需要的数组长度,是可以再去容纳一个元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
 //这里传入的是size+1
 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }
//这个方法是判断数组是否是一个空数组,要是使用无参构造函数初始化的arrayList,那么返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //要是elementData是一个空的数组
        //判断需要的数组长度和默认容量值哪个大,返回最大的那个
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //要是elementData数组中有元素,直接返回minCapacity
        return minCapacity;
}
//这里是,判断数组是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
        //记录数组被修改的次数
        modCount++;

        //要是需要的数组长度大于目前数组的长度,就需要扩容了(即数组的长度是否可以存入下一个元素)
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}
 /**
     * 扩容方法
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        //旧的数组的长度
        int oldCapacity = elementData.length;
        //需要扩容的数组的长度,即10*1.5=15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //要是扩容后的数组的长度还是小于需要的最小容量,那么就把需要的最小容量给newCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //要是扩容后的数组长度比最大的数组容量还大,就需要控制了
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //把扩容之后的数组copy到长度为newCapacity的数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        //参数小于0.抛异常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //判断需要的数组容量和数组最大容量哪个大,
        //需要的数组容量比数组最大容量还大,就返回int的最大值
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

图式:
在这里插入图片描述

ArrayList中的add(插入指定位置)方法

先看下 System.arraycopy:

    // src 源数组
    // srcPos 源数组要复制的起始位置
    // dest 要赋值到的目标数组
    // destPos 目标数组放置的起始位置
    // length 复制的长度
   System.arraycopy(Object src,  int  srcPos, Object dest, int destPos,int length);

继续add方法:

    /**
     * 将元素插入数组中指定的位置
     * @param index 指定的索引值
     * @param element 需要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //参数校验,判断要插入的下标是否越界
        rangeCheckForAdd(index);
        //这个和add(E e)是一样的,判断数组是否需要扩容等
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把插入index位置的原有元素以及该元素后面的元素,向右移动
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //把元素插入到index的位置
        elementData[index] = element;
        //数组元素的个数加1
        size++;
    }

    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        //要是插入的位置比数组中元素的个数大,或者插入的位置值小于0,就抛出下标越界异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * Constructs an IndexOutOfBoundsException detail message.
     * Of the many possible refactorings of the error handling code,
     * this "outlining" performs best with both server and client VMs.
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

在这里插入图片描述

ArrayList中的get方法

get方法相对而言就比较简单些:

 /**
     * 根据下标获取指定位置的元素
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        //参数校验,若index比数组中元素的size还大,就抛异常
        rangeCheck(index);
        //返回对应的元素
        return elementData(index);
    }
 /**
     * 在获取数组元素之前,需要进行数据校验
     * 若传入的参数不在指定的数组索引范围内,就抛异常
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

ArrayList中的remove(int index)方法

/**
     * 移除列表中指定位置的元素,然后把移除元素后面的元素向左移动
     *
     * @param index 需要移除元素的索引值
     * @return 移除的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        //参数校验,校验index的值是否在索引所允许的范围内
        rangeCheck(index);
        //列表的修改次数加一
        modCount++;
        //先查出对应index位置出的元素,赋值给oldValue
        E oldValue = elementData(index);
        //把移除的元素后面的所有元素向左移动
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //把数组最后一个索引位置数据设置为null
        elementData[--size] = null; // clear to let GC do its work
        //返回移除的元素数据
        return oldValue;
    }

在这里插入图片描述

ArrayList中的remove(Object o)方法

    /**
     *
     * 从列表中删除指定元素的第一个匹配项
     *
     * @param o 需要移除的元素
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        //若元素是null,移除第一个匹配为null的元素
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        //若元素不为null,就移除第一个匹配到的元素
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        //列表修改次数加一
        modCount++;
        //同样的,是把移除元素之后的所有元素向左移动
        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
    }

注意:由于ArrayList是线程不安全的,所以不要在遍历中去对ArrayList做修改,否则会出现错误

ArrayList中的clear方法

/**
     * 清除列表中的所有元素
     */
    public void clear() {
        //列表修改次数加一
        modCount++;
        // clear to let GC do its work 清除所有元素,垃圾回收
        //通过遍历把所有元素设置为Null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

CopyOnWriteArrayList源码解析

在这里插入图片描述
通过上图可以看到copyOnWrite的实现方式,这种方式适用于读极多,写极少的情况,而且如果数据量巨大,在copy之后的一瞬间,内存占用增加,也会引发问题。CopyOnWriteArrayList是线程安全的。

CopyOnWriteArrayList变量

    /** 可重入锁 */
    final transient ReentrantLock lock = new ReentrantLock();

    /**
     * 数组,只能通过getArray和setArray操作
     */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

CopyOnWriteArrayList的构造函数

 /**
     * 创建一个空的列表
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
     *
     * @param c 传入的集合
     * @throws NullPointerException if the specified collection is null
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    /**
     * 创建包含给定数组副本的列表
     *
     * @param toCopyIn the array (a copy of this array is used as the
     *        internal array)
     * @throws NullPointerException if the specified array is null
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

CopyOnWriteArrayList的两个add方法

    /**
     * 添加一个元素到列表的最后面
     *
     * @param e 需要添加到列表中的元素
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //初始化锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取数组中的元素
            Object[] elements = getArray();
            //获取数组的长度
            int len = elements.length;
            //把elements数组copy到长度为len + 1的newElements数组中,即新的数组长度增加1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //然后把元素加到数组的末尾
            newElements[len] = e;
            //set数组的元素为添加之后的数组
            setArray(newElements);
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 向指定的索引位置加入元素,加入位置后面的元素需要向右移位
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //初始化一个锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取数组中的元素
            Object[] elements = getArray();
            //获取数组的长度
            int len = elements.length;
            //如果传入的索引值不在数组所允许的范围内,就抛异常
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            //如果插入的索引大小和数组长度一样,那么直接插入到数组末尾
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                //设置新数组的长度比之前的数组长度大一位,即可以插入一个元素
                newElements = new Object[len + 1];
                //先copy elements中index索引之前的元素到newElements
                System.arraycopy(elements, 0, newElements, 0, index);
                //再把elements中index之后的元素已经index中的元素copy到index右边,即右移
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //把元素放入到指定的索引处
            newElements[index] = element;
            //设置数组的引用为新的数组
            setArray(newElements);
        } finally {
            //解锁
            lock.unlock();
        }
    }

向指定索引处插入元素图解:
在这里插入图片描述

CopyOnWriteArrayList的两个get方法

get方法比较简单,不做赘述。

private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

CopyOnWriteArrayList的remove方法

    /**
     * 移除列表中指定索引位置的元素,并把后续的元素向左移动
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        //初始化锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取数组中的元素
            Object[] elements = getArray();
            //获取数组的长度
            int len = elements.length;
            //获取索引处原先的旧值
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            //如果要移除的是最后一位直接移除
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //新的数组,长度比旧的数组少一位
                Object[] newElements = new Object[len - 1];
                //同样的,//先copy elements中index索引之前的元素到newElements
                System.arraycopy(elements, 0, newElements, 0, index);
                //再把elements中index+1之后的元素向左移位
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            //返回移除后的元素
            return oldValue;
        } finally {
            //解锁
            lock.unlock();
        }
    }

在CopyOnWriteArrayList,移除的方法还有另外两个,实现的方法也都大同小异,都是先copy一份列表,然后加锁去操作,移除掉元素,然后再把数组的引用指向移除后的数组即可。

到此,arrayList和CopyOnWriteArrayList源码就结束了,上面的解释以及注释可能有错误或者不足的地方,希望指正,共同进步,多谢!

发布了21 篇原创文章 · 获赞 8 · 访问量 4367

猜你喜欢

转载自blog.csdn.net/qq_34365173/article/details/105427173