基于JDK8的ArrayList源码剖析

前言

ArrayList是我们开发过程中经常用到的集合类,一般涉及到集合操作的业务都会想到它,在java的集合家族中,知名度最高的集合类非他莫属。但是人无完人,ArrayList也存在利与弊,在开发过程中如何权衡呢?让我们通过源码对ArrayList底层进行一个深入的了解,这样就能在使用过程中根据需求来决定是否使用该集合类。

走近ArrayList

ArrayList基于数组实现,在普通数组基础上增加了自动扩容等功能。实现了List, RandomAccess, Cloneable, java.io.Serializable接口。

  • List接口: 封装了集合的一些基本操作,比如存储的元素数量,集合是否为空,是否包含某个元素等。
  • RandomAccess接口: 是一个标志接口,表示这个当前实现类支持快速随机访问。在ArrayList中,我们可以通过元素下标快速获取对应元素,这就是快速随机访问。
  • java.io.Serializable接口: 说明ArrayList支持序列化,能通过序列化在网络中进行传输。

属性

ArrayList属性主要包括存放元素的Object数组、已存放元素的数量以及从AbstractList继承过来的modCount。

	// 序列化ID
	private static final long serialVersionUID = 8683452581122892189L;

    // 默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;

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

    // 用于默认大小的空实例的共享空数组实例,将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要膨胀多少
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 真正存放元素的数组, transient表明不参与序列化
    transient Object[] elementData; // non-private to simplify nested class access

    // 数组包含元素的个数
    private int size;

构造方法

ArrayList有三个构造方法,分别是创建指定容量大小的 ArrayList(int initialCapacity),创建默认数组的 ArrayList(),传入一个集合的 ArrayList(Collection<? extends E> c)

创建指定容量大小的构造方法

传入的参数initialCapacity代表初始数组长度,若initialCapacity >= 0,根据initialCapacity 构造对应大小的数组;否则抛出异常。

	public ArrayList(int initialCapacity) {
		// initialCapacity > 0,根据创建容量为initialCapacity 的数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        }
        //  initialCapacity > 0,将EMPTY_ELEMENTDATA赋值给elementData
        else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        }
        //  initialCapacity < 0,抛出异常
        else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

无参构造方法

若不传入参数,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行初始化。

	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

带集合构造方法

将传入的集合转换成数组,对转换后的数组进行长度判断,若长度不等于0,即大于0,则使用Arrays.copyOf方法转换后的数组内容深拷贝到elementData。若长度为0,直接将空对象EMPTY_ELEMENTDATA地址赋值给elementData。

	public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray可能不返回Object[],需要判断
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 若c.toArray不返回Object[],用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

常用方法

add方法

该方法往数组末尾添加元素,添加前调用ensureCapacityInternal方法确保当前数组大小可以容纳添加的元素。

	public boolean add(E e) {
		// 确保数组大小足够
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

来看下ensureCapacityInternal方法:

	private void ensureCapacityInternal(int minCapacity) {
		// 如果是通过无参构造方法创建,使用DEFAULT_CAPACITY和minCapacity两者之间最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    
	private void ensureExplicitCapacity(int minCapacity) {
		// 修改次数加1,用于fail-fast机制
        modCount++;
        // 如果minCapacity大于elementData数组长度,需要进行扩容
        if (minCapacity - elementData.length > 0)
        	// 进行扩容操作
            grow(minCapacity);
    }

进入扩容方法grow

	private void grow(int minCapacity) {
        // oldCapacity为旧容量大小,newCapacity为新容量大小
        int oldCapacity = elementData.length;
        // oldCapacity >> 1相当于oldCapacity / 2,所以newCapacity的大小是oldCapacity的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 若1.5倍后的新容量小于需要容量,直接将需要的容量值作为新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 判断新容量大小是否超过最大容量限制
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 使用深拷贝赋值
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static int hugeCapacity(int minCapacity) {
    	// 判断是否溢出
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    

小结:
先判断容量是否足够,若足够,则直接添加到数组末尾,不够就先进行扩容再添加。扩容需要注意以下两点:

  • 默认扩容一半,如果扩容一半后还不够,就用目标的容量大小作为扩容后的容量。
  • 扩容成功需要更新modCount值。

remove方法

我们比较常用的删除方法是根据下标删除,根据指定元素删除。即remove(int index)remove(Object o)

remove(int index)

根据元素下标删除。

	public E remove(int index) {
		// 判断传入的下标是否越界
        rangeCheck(index);
		// 由于数组结构有变化,所以modCount值加1
        modCount++;
        // 获取传入下标的值,用于返回
        E oldValue = elementData(index);

		// 计算System.arraycopy方法需要的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 用index后面的数据覆盖掉前面
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将最后一个元素置空,用于GC回收
        elementData[--size] = null; 

        return oldValue;
    }
remove(Object o)

根据元素删除,若有多个相同元素则删除第一个出现的。返回true表示删除成功,false表示删除失败。

	public boolean remove(Object o) {
		// 传入Object为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;
    }

小结:
根据下标删除和根据元素删除底层都是数组的复制,相对低效,且要修改modCount的值。

set方法

覆盖指定下标的元素。

	public E set(int index, E element) {
		// 检查传入的下标是否越界
        rangeCheck(index);
		// 返回修改前的旧值
        E oldValue = elementData(index);
        // 将指定下标的元素替换为新的值
        elementData[index] = element;
        return oldValue;
    }

小结:
检查是否越界,获取旧值,将指定下标的值替换为新值,返回旧值。

get方法

获取指定下标的元素。

	public E get(int index) {
		// 检查传入的下标是否越界
        rangeCheck(index);
		// 返回对应下标的元素值
        return elementData(index);
    }

小结:
检查是否越界,返回对应下标的元素值。

总结

ArrayList源码总体来说不算太难,代码逻辑比较容易理解。有一些细节的方法我就没展开分析。希望这篇文章对你有所帮助,有什么问题和建议欢迎随时交流~

参考链接
JavaGuide-ArrayList源码+扩容机制分析
面试必备:ArrayList源码解析(JDK8)
ArrayList源码分析(基于JDK8)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值