Java中ArrayList的详解(源码分析)

183 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第十一天,点击查看活动详情

ArrayList的详解(源码分析)

前言

JDK 1.8的源代码分析

我们在开发和面试过程中,ArrayList是我们比较熟悉的对象,也是集合框架中重要的体系,ArrayList从名字就可以看出来是实现了List接口的可变数组,我们先看一下ArrayList的结构图

4761309-df5867f49d55416a.png

我们通过这个结构图,来简单介绍一下,每个接口和类的作用

  • Collection接口Collection接口是所有集合的根节点,它定义了一组允许重复的对象

  • List接口List是继承与Collection接口,List接口的集合类中的元素是对象有序且可重复

  • AbstractCollection抽象类:主要是实现了Collection接口的contains()toArray()removeAll()toString()

  • AbstractList抽象类:主要实现了List接口的方法

  • Cloneable接口:它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中

  • Serializable接口:表示可以序列化和反序列化

  • RandomAccess接口:该接口表示可以支持快速随机访问

ArrayList实现原理

核心属性

默认容量,即为初始值大小
private static final int DEFAULT_CAPACITY = 10;

共享的空数组,用于初始化空实例
private static final Object[] EMPTY_ELEMENTDATA = {};

存储 ArrayList 元素的数组缓冲区
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList内部结构,是一个Object[]类型的数组
transient Object[] elementData; 
 
数组长度大小
private int size;

初始化

我们来看一下ArrayList的构造函数

public ArrayList(int initialCapacity) {}
public ArrayList() {}
public ArrayList(Collection<? extends E> c) {}

我们发现创建ArrayList有三种方式

ArrayList<String> strings = new ArrayList<>(20);

我们在创建ArrayList的时候直接指定初始化大小,这样可以有效的避免在添加元素的时候,进行不必要的扩容

ArrayList<String> strings = new ArrayList<String>();

构造一个初始容量为十的空列表。

List<String> list = new ArrayList<>();
ArrayList<String> strings = new ArrayList<>(list);

构造一个包含指定collection的元素的列表,这些元素是按照该collection的迭代器返回它们的顺序排列的。

ArrayList添加元素

我们用add(E e)方法来进入对元素的添加,add()源代码如下:

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

我们发现add()就只有三行代码,我们主要关注ensureCapacityInternal()就行,这个方法就是大家面试常备问道的扩展,那我们来看看内部实现

ArrayList的add()

我们知道ArrayList的扩容机制就是在ensureCapacityInternal()中,我们先来看看该方法的源码

private void ensureCapacityInternal(int minCapacity) {
    //判断是否为一次添加
    //(ArrayList在初次创建的时候并没有指定大小,
    //elementData会被初始化成一个空数组,也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //设置初始化大小默认为DEFAULT_CAPACITY也就是10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    
    
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    //判断长度是否大于数组长度
    if (minCapacity - elementData.length > 0)
        //扩容
        grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //扩容到原来的3/2长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //判断扩容后是否比最小容量还要小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //判断扩容后是否比最大容量大
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

我们发现add(int index, E element)是可以在指定索引下添加,那么ArrayList是如何实现的呢?我们看一下源码

public void add(int index, E element) {
    //检测索引是否正确
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    //和之前一样
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

/**
 * 
 * @param src      数据源
 * @param srcPos   数据源开始的索引
 * @param dest     目标数据
 * @param destPos  目标数据的开始索引
 * @param length   复制的长度
 */
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

ArrayList的set

我们需要更新指定索引的数据,我们需要用到set方法,我们线以下源码:

public E set(int index, E element) {
    //判断索引是否正确
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
    //获取该索引值
    E oldValue = (E) elementData[index];
    //复制新值
    elementData[index] = element;
    //返回就值
    return oldValue;
}

使用:

strings.set(0,"zhangsan");

ArrayList的remove

ArrayList删除数据我们需要用到remove(),但是remove()常用的有两个,一个是删除指定索引remove(int index),一个是删除指定数据remove(Object o)

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    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

    return oldValue;
}

public boolean remove(Object o) {
    //判断是否null
    if (o == null) {
        //遍历数组
        for (int index = 0; index < size; index++)
            //找到元素
            if (elementData[index] == null) {
                //移除
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}