ArrayList源码解析

ArrayList源码解析

ArrayList介绍

ArrayList是我们集合框架中最常使用的数据结构之一。它实现了RandomAccess接口,支持快速随机访问。底层基于数组实现容量大小的动态变化,下面是ArrayList的类图:

在这里插入图片描述

  • AbstractCollection:该抽象类是Collection接口的一个基本实现,提供了部分操作集合的方法

  • AbstractList是一个抽象类,实现了List接口。提供了一部分操作集合的方法,和get(int index)等抽象方法

RandomAccess:“随机访问”接口,实现了这个接口的类是支持快速随机访问的。代码注释中还特意说明:如果实现了这个接口,for循环速度方式会优于迭代器:

/*
 * <pre>
 *     for (int i=0, n=list.size(); i &lt; n; i++)
 *         list.get(i);
 * </pre>
 * runs faster than this loop:
 * <pre>
 *     for (Iterator i=list.iterator(); i.hasNext(); )
 *         i.next();
 * </pre>
 */
public interface RandomAccess {
}

1.8 中的 ArrayList

Fields

// ArrayList的默认容量为10
private static final int DEFAULT_CAPACITY = 10;
// 供空实例使用的共享空数组实例,容量可变
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认大小时的空数组实例,new空数组时,将会赋值给空数组,容量固定
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储ArrayList元素的数组缓冲区,作为指向数组数据的指针
transient Object[] elementData;
// 存储数组的长度
private int size;
// 数组的最大长度,-8是因为需要保留一些空间存储对象头等信息
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 集合被修改的次数
protected transient int modCount = 0;

Arraylist 的构造方法

  1. 无参构造:由该函数可以看出,在使用无参构造创建ArrayList数组时,并不会初始化ArrayList的长度,而是在使用时(添加元素)才初始化。

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  2. 有参构造1:该构造函数的入参为数组的初始长度(容量),在初始化ArrayList时,首先判断入参容量的大小,若大于0,则会初始化一个容量为 initialCapacity 大小的Object数组作为该ArrayList的初始状态存在;若入参为0,则会将常量中的EMPTY_ELEMENTDATA空数组赋值给该数组,避免创建多余的空对象数组;当入参小于零时,报异常:

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	// 初始化容量为 initialCapacity
        	this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	// 容量为0时使用常量EMPTY_ELEMENTDATA
        	this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
            initialCapacity);
        }
    }
    
  3. 有参构造2:入参为“要将其元素放入此列表中的集合”,在该有参构造中,首先将elementData指向c.toArray(),然后判断当前数组长度是否为0;如果为0,则将当前ArrayList初始化为空;否则就判断当前elementData指向的类型是否为Object[],如果不是,则使用Arrays.copyOf()方法复制一块新的内存区域存储转换为Object[]类型的c数组。Arrays.copyOf()方法底层使用System.arraycopy()实现数组的复制。代码如下:

    public ArrayList(Collection<? extends E> c) {
        // 修改elementData指向
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {// 判断是否为空
    		// 如果不是对象数组类型就初始化为对象数组类型
        	if (elementData.getClass() != Object[].class)
        		elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 集合为空则初始化为空
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
    /*
     * Arrays.copyOf()中的部分代码
     *	original:元数据
     *  copy:要复制到的目标数组
     */
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    

常用方法的源码

add(E e):向列表的末尾添加元素

该方法的作用是将指定的元素添加到集合的末尾。在该方法中,首先判断当前集合长度是否符合要求,在ensureCapacityInternal()方法中,当集合未初始化时,会首先初始化集合长度(ArrayList的无参构造未做初始化操作),然后使用ensureExplicitCapacity()方法提升集合中数组长度。在ensureExplicitCapacity()方法中,使用了grow()方法增加数组长度。由grow()方法可见,正常情况下,ArrayList的扩容操作会将容量提升至原容量的1.5倍,超长时,依据情况将容量置为最大容量或Integer的最大值。

public boolean add(E e) {
   	// 判断数组容量大小
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}


private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //数组为空时,取DEFAULT_CAPACITY, minCapacity大的一方作为数组容量
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
	// 提升数组长度
    ensureExplicitCapacity(minCapacity);
}


private void ensureExplicitCapacity(int minCapacity) {
    // 修改次数+1
    modCount++;
    //容量大于数组长度时,提升数组的长度
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // 获取原数组长度
    int oldCapacity = elementData.length;
    // 新的数组长度等于旧数组长度+旧长度右移一位(即1.5倍长度)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        // 1.5倍容量小于要增长的容量时,将容量设置为newCapacity
        newCapacity = minCapacity; 
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 新数组长度大于数组最大容量,使用hugeCapacity()获取新容量
        newCapacity = hugeCapacity(minCapacity);
    // 复制
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 当容量大小超过定义的数组最大容量时,返回Integer的最大值作为数组长度
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}
get(int index):获取指定位置的元素

ArrayList中的get()方法用于获取集合在指定索引位置存储的元素

public E get(int index) {
    // 检查是否越界
    rangeCheck(index);
	// 获得elementData index位置上的元素
    return elementData(index);
}
// 检查索引位置是否有效
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 获取元素
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}
set(int index, E element): 修改指定索引位置的值

ArrayList中的set()方法用于将此列表中指定位置的元素替换为指定元素,返回值为被替换掉的旧值。

public E set(int index, E element) {
	// 检查索引是否越界
    rangeCheck(index);
	// 取出原始值
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
remove(int index):删除指定位置的元素

用于删除此列表中指定位置的元素并返回旧值,并将后面的元素向左移动。

public E remove(int index) {
    rangeCheck(index);
	// 修改次数+1
    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;
}

总结

从ArrayList中我们常用的增删改查方法可以看出:在向ArrayList集合中添加或删除元素时,基本都涉及到对原数组修改后的移动(其实是在内存中的复制)操作,在频繁的增删操作中,性能较低。而得益于数组结构的优越性,在获取元素方面,ArrayList拥有很快的响应速度,因此在ArrayList元素的获取和修改方面,显得效率较高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值