JAVA集合类之ArrayList源码解析

本文深入解析ArrayList的内部实现,包括其动态扩展机制、初始化过程、核心方法如add、remove、set、get等的工作原理,以及ArrayList与LinkedList在性能上的对比。

一、ArrayList简介

  • ArrayList是一个数组队列,相当于动态数组,基于List接口实现。核心由Object[]数组组成,添加元素的时候,数组的长度会自动扩展。
  • ArrayList不是一个线程安全类,与之相对应的是同样基于数组实现的线程安全集合类Vector,除线程安全外,他们最大的区别为动态扩展时ArrayList增长为原来的1.5倍,Vector为原来的2倍
  • ArrayList的初始容量为10,增长因子为0.5
  • ArrayList继承结构简图
    在这里插入图片描述

二、ArrayList源码解析

1. 继承结构
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • AbstractList
    • AbstractList中List对抽象类中的部分方法进行了实现,对子类中的方法进行了规范,同时也方便其扩展
  • List
    • 据作者说这是一个错误,因为AbstractList也同样实现了List接口,但因为没影响,就沿用至今
  • RandomAccess
    • 这是一个标记性接口,意义是: 在对列表进行随机或顺序访问的时候,访问算法能够选择性能最佳方式。 在for循环遍历的时候,能提高性能
  • Cloneable
    • 标明其可以实现克隆操作,主要为Object.clone()方法
  • Serializable
    • 序列化接口,标明其可以被序列化,主要用于字节流的传输
2. 属性
//序列化版本,主要用于序列化时判断版本是否一致
private static final long serialVersionUID = 8683452581122892189L;
//初始容量
private static final int DEFAULT_CAPACITY = 10;
//空对象数组,在后续代码需要时使用的空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//初始化空对象数组,在初始化时提供空数组对象(这个时候是没有长度的,会在add方法中初始化长度为10)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//核心元素数组,用于储存元素,不参与序列化
transient Object[] elementData;
//实际元素数组长度,默认为0
private int size;
//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//记录修改的次数,继承自AbstractList
protected transient int modCount = 0;
3. 构造方法
  • 无参构造
/*
 * 为核心元素数组赋值为初始化空对象数组,这个时候还未初始化长度,会在add方法中初始化长度为10
 */
public ArrayList() {
    //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是个空的Object[],用它初始化elementData。
    //这个空的Object[]的初始容量为10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • 有参构造1
/*
 * 带初始化容量大小的构造方法,如果数值合法,会将核心元素数组初始化为相应大小数组
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //如果该值大于0,为核心元素数组初始化相应大小的空数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果该值等于0,为核心元素数组初始化空对象数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //如果该值为其他值,抛出非法数据异常
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}
  • 有参构造2
/**
 * 传入一个集合,通过这个集合来初始化ArrayList
 * 集合中指定泛型的类型必须为ArrayList指定类型的子类
 * 该类不常用
 */
public ArrayList(Collection<? extends E> c) {
    //调用toArray()方法转化为数组,并赋值给核心元素数组
    elementData = c.toArray();
    //判断数组长度
    if ((size = elementData.length) != 0) {
        //每个集合的toArray()方法不一样
        //如果不是Object[].class类型,则通过Arrays.copyOf将其改造为Object[].class类型
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 如果传入集合为空,为核心元素数组初始化空对象数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
4. 核心方法
4.1 add方法
  • boolean add(E e):在List末尾添加元素

    • 判断是否需要初始化,需要的话执行初始化
    • 判断是否需要扩展,需要的话调用grow方法,将原长度扩展到以前的1.5倍
    • 数组最后一位放入传入元素,长度+1
/**
 * 在List末尾添加元素
 */
public boolean add(E e) {
    //确定容量是否足够,因为要添加一个元素,所以传入size+1进行判断
    ensureCapacityInternal(size + 1);
    //size+1,并将其放入正确位置
    elementData[size++] = e;
    return true;
}

/**
 * 确定内部容量
 */
private void ensureCapacityInternal(int minCapacity) {
    //先调用calculateCapacity判断实际需要内部容量大小
    //再调用ensureExplicitCapacity将实际容量和需要容量对比,如果不足,执行增长
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/**
 * 用于确定内部容量长度
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //先判断有没有初始化核心数组的容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //返回值为minCapacity和DEFAULT_CAPACITY的较大值,一般情况下返回10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //如果初始化过了则返回传入的容量
    return minCapacity;
}

/**
 * 判断实际容量是否满足add之后的容量大小,如果不满足,则执行增长
 */
private void ensureExplicitCapacity(int minCapacity) {
    //修改次数+1
    modCount++;
    //如果所需长度大于实际长度,执行增长
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * ArrayList的核心方法,用于扩展数组大小。
 */
private void grow(int minCapacity) {
    //将扩充前的elementData的长度赋值给oldCapacity
    int oldCapacity = elementData.length;
    //将新的容量newCapacity扩充到oldCapacity的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //这种情况主要出现在elementData=EMPTY_ELEMENTDATA时,即newCapacity=0时
    //将新的长度设置为minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果newCapacity大于最大容量,调用hugeCapacity赋予最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 新的容量已经确定好了,则调用copyOf,改变容量大小完成扩展
    elementData = Arrays.copyOf(elementData, newCapacity);
}

/**
 * 用于赋最大值
 */
private static int hugeCapacity(int minCapacity) {
    //传入值大于0,不多解释
    if (minCapacity < 0) 
        throw new OutOfMemoryError();
    //从MAX_ARRAY_SIZE初始化中可以看到MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
    //如果minCapacity大于MAX_ARRAY_SIZE,则返回MAX_ARRAY_SIZE,相当于两层防护
    return (minCapacity > MAX_ARRAY_SIZE) ?
         Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
 }
  • void add(int index, E element):在指定位置添加元素

    • 判断插入位置是否合理
    • 判断是否需要初始化,需要的话执行初始化
    • 判断是否需要扩展,需要的话调用grow方法,将原长度扩展到以前的1.5倍
    • 执行System.arraycopy方法对原数组执行添加并扩展操作
/**
 * 在指定位置添加元素
 */
public void add(int index, E element) {
    //判断插入位置是否合理
    rangeCheckForAdd(index);
    //确定容量,同上
    ensureCapacityInternal(size + 1);
    //将index位以及之后的元素依次往后挪一位
    //5个参数意义分别是:
    //源数组,源数组要复制的起始位置,目标数组,目标数组粘贴的起始位置,进行copy的数组长度
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //将输入元素放入正确位置
    elementData[index] = element;
    //长度增长1
    size++;
}
 
/**
 * 判断插入位置是否合理
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • boolean addAll(Collection<? extends E> c)、boolean addAll(int index, Collection<? extends E> c)
    • 以上两种方法分别是在集合末尾或指定位置添加集合类元素
    • 这两种方法的底层实现和add(int index, E element)类似,都是:
      • 先判断是否需要初始化或扩展,需要的话执行初始化或扩展
      • 再执行System.arraycopy方法对原数组执行添加并扩展操作
      • 可自行查看源码,故不再累述
  • 总结
    • add方法可以在List末尾添加元素或者在指定位置添加元素
    • 为默认空数组初始化默认长度10的过程在add方法中
    • add方法会在容量不够的时候调用grow方法,默认扩展到原来的1.5倍,特殊情况下:
      • 原长度为0,则赋值初始值
      • 新扩展数组大小已经达到最大值,只会扩容到Integer.MAX_VALUE
4.2 remove方法
  • E remove(int index):移除指定位置元素,并返回该元素的值
    • 检查取值是否合理
    • 执行System.arraycopy方法对原数组执行删除并缩短操作
    • 将最后一位置为null,方便GC回收
/**
 * 移除指定位置元素,并返回该元素的值
 */
public E remove(int index) {
    //判断index取值是否合理
    rangeCheck(index);
    //修改次数+1
    modCount++;
    //取出index位下标元素的值,便于返回
    E oldValue = elementData(index);
    //计算出进行copy的数组长度
    int numMoved = size - index - 1;
    //如果本来就是最后一位,则不进行复制,提高效率
    if (numMoved > 0)
        //执行数组复制,将index后一位以及之后的元素全部前移一位,原理同上
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
 //将最后一位置为null,以便于GC能在整个数组不使用时进行回收
    elementData[--size] = null;
 //返回index位元素的值
    return oldValue;
}

/**
 * 判断index取值是否合理
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * 获取当前下标元素的值
 */
E elementData(int index) {
    return (E) elementData[index];
}
  • boolean remove(Object o):移除指定元素,返回是否成功(如果有相同元素,只移除第一个)
    • 找到指定元素的index
    • 执行fastRemove(int index)移除指定元素,这是一个和remove(int index)一样但无返回值的私有方法
/**
 * 移除指定元素,返回是否成功
 */
public boolean remove(Object o) {
    //先判断是否为空,主要是用于通过不同的方式判断元素是否存在
    if (o == null) {
        //遍历找出为空的元素下标,调用fastRemove将其移除,并返回true
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //遍历找出为Object o的元素下标,调用fastRemove将其移除,并返回true
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
             fastRemove(index);
                return true;
            }
    }
    //未找到该元素,返回false
    return false;
}

/**
 * 移除指定位置元素,相当于没有返回值的E remove(int index)
 */
private void fastRemove(int index) {
    //修改次数+1
    modCount++;
    //取出index位下标元素的值,便于返回
    int numMoved = size - index - 1;
    //如果本来就是最后一位,则不进行复制,提高效率
  if (numMoved > 0)
        //执行数组复制,将index后一位以及之后的元素全部前移一位,原理同上
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    //将最后一位置为null,以便于GC能在整个数组不使用时进行回收
  elementData[--size] = null;
}
  • boolean removeAll(Collection<?> c):批量删除,移除指定集合内包含的所有元素
    • 先验证传入元素合法性
    • 再将不重复的n位元素放在数组前n位
    • 将n位以后的的元素置为null,完成批量删除
/**
 * 批量删除,移除指定集合内包含的所有元素
 */
public boolean removeAll(Collection<?> c) {
    //Objects工具类方法,判断传入的集合c是否为null
    Objects.requireNonNull(c);
    //调用并返回批量删除方法,删除相应元素
    return batchRemove(c, false);
}
/**
 * 批量删除方法
 * complement为false用于removeAll方法,用于执行批量删除
 * complement为true用于retainAll方法,用于判断两个集合是否有交集
 */
private boolean batchRemove(Collection<?> c, boolean complement) {
    //定义一个内部变量,用于保存原核心元素数组,将其记为M
	final Object[] elementData = this.elementData;
    //其中r用来控制循环,w用来记录有多少个交集元素
	int r = 0, w = 0;
    //初始化返回值
	boolean modified = false;
	try {
        //遍历
		for (; r < size; r++)
            //如果complement为false,removeAll调用:
            //则M的前w位元素为两个集合不同的元素,用于移除
            //如果complement为true,retainAll调用:
            //则M的前w位元素为两个集合相同的元素,用于交集
			if (c.contains(elementData[r]) == complement)
				elementData[w++] = elementData[r];
	} finally {
		// r != size的情况只会发生在遍历过程中发生异常,即contains方法发生异常时
		if (r != size) {
            //将异常点前遍历操作取出的元素,和异常点后剩下的元素拼接起来形成新的数组
            //如果removeAll调用,新数组为异常点前不重复的元素和异常点后的元素
            //即只执行了异常点前的移除操作
            //如果retainAll调用,新数组为异常点前重复的元素和异常点后的元素
            //即只执行了异常点前取交集,再拼接异常点后元素
			System.arraycopy(elementData, r, elementData, w, size - r);
            //这一步主要用于之后的循环判断置为null的元素位数
			w += size - r;
		}
        //如果w == size,此时集合任为原集合
        //如果removeAll调用,证明两个集合没有交集,移除后任为M本身
        //如果retainAll调用,证明c中包含M,交集为M
		if (w != size) {
			//这一系列操作后,集合中只会留下相应的非交集(removeAll)或交集元素(retainAll)
            //主要取决于complement的取值
			for (int i = w; i < size; i++)
                //置为null方便GC
				elementData[i] = null;
			modCount += size - w;
			size = w;
			modified = true;
		}
	}
    //removeAll执行结束后,移除了元素返回true,否则返回false
    //retainAll执行结束后,只有证明c中包含M或者相等时返回false,启用情况返回true
    //故一般retainAll不能通过返回值来判断是否取交集成功,应该按照操作后元素个数来进行判断
	return modified;
}
  • 总结
    • remove系列方法先把不重复的n个元素放在前数组的前n位
    • 然后把n位以后的元素置为null,方便GC调用
4.3 set方法
  • E set(int index, E element):为相应下标元素设置相应的值
    • 先验证下标合法性
    • 再赋予新值,返回旧值
/**
 * 为相应下标元素设置相应的值
 */
public E set(int index, E element) {
    //检查index是否合理
	rangeCheck(index);
    //取出旧值
	E oldValue = elementData(index);
    //赋予新值
	elementData[index] = element;
    //返回旧值
	return oldValue;
}
  • 总结
    • 核心为数组的设值操作
4.4 get方法
  • E get(int index):返回相应下标的值
    • 先验证下标合法性
    • 再返回相应下标的值
/**
 * 返回相应下标的值
 */
public E get(int index) {
    //检查index是否合理
    rangeCheck(index);
    //返回相应下标的值
    return elementData(index);
}
  • 总结
    • 核心为数组的取值操作
4.5 indexOf方法
  • int indexOf(Object o):返回传入对象的位置,如果有重复的对象,只会返回第一个的位置
    • 遍历取出传入对象的位置
/**
 * 返回传入对象的位置
 */
public int indexOf(Object o) {
    //先判断是否为空,主要是用于通过不同的方式判断元素是否存在
	if (o == null) {
        //返回第一个为null对象的位置
		for (int i = 0; i < size; i++)
			if (elementData[i]==null)
				return i;
	} else {
        //返回第一个和o相等的对象的位置
		for (int i = 0; i < size; i++)
			if (o.equals(elementData[i]))
				return i;
	}
    //如果没找到就返回-1
	return -1;
}
  • 总结
    • 核心思想就是遍历,比较,返回位置
4.6 retainAll方法
  • boolean retainAll(Collection<?> c):求交集,移除非交集元素
    • 先验证数据合理性
    • 再调用batchRemove移除非交集元素,剩下交集元素
/**
 * 求交集,执行后集合剩下交集元素
 */
public boolean retainAll(Collection<?> c) {
    //Objects工具类方法,判断传入的集合c是否为null
    Objects.requireNonNull(c);
    //调用并返回批量删除方法,删除非交集元素,具体参考removeAll
    return batchRemove(c, true);
}
  • 总结
    • retainAll重复的n位元素放在数组前n位,然后将n位以后的的元素置为null,完成求交集
    • retainAll不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交集。
4.7 clear方法
  • void clear():清空集合
    • 将集合中所有值置为null
    • 将集合size置为0
public void clear() {
    //修改次数+1
    modCount++;
    //所有对象置为null,断开引用,gc回收这些对象
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    //数组长度重置为0
    size = 0;
}
  • 总结
    • clear()会将所有对象置为null,即断开这些对象的引用,以便于GC回收他们
    • clear()之后核心元素数组仍会存在于堆内存中,仍被引用,不会被GC回收,故任可调用add等方法进行操作

三、总结

  1. ArrayList可以存放重复元素,也可以存放空值,但是执行remove、indexOf等一系列方法时,都只会操作重复元素的第一个
  2. ArrayList本质上就是一个elementData数组,但其可以动态扩展大小,在add系列方法中调用grow方法进行扩展
  3. ArrayList默认长度为10,也可以通过带参数的构造方法自定义初始长度
  4. ArrayList默认最大长度为Integer.MAX_VALUE - 8,特殊情况下,也可能扩展到Integer.MAX_VALUE
  5. ArrayList默认增长因子为0.5,每次动态扩展默认扩展到原来的1.5倍,不可修改
  6. ArrayList的核心是数组,这也就导致了它在查询方面优势明显,但插入和删除的时候性能会有较大劣势,所有查询多用ArrayList,修改多用LinkedList
  7. ArrayList实现了RandomAccess,所以在遍历它的时候使用for循环性能较好
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值