ArrayList源码分析(基于1.8版本)

深入解析Java中ArrayList的源码,探讨其底层数据结构、动态扩容机制、成员变量及构造方法,理解添加、排序、克隆等核心方法的实现。

Java中,ArrayList是很常用的集合类之一。之前一直是机械使用,这次翻下源码,加深对该集合类的
理解,以便能更好的使用。

本文只会对源码进行解析,不会对底层数据结构及算法进行过多分析探究.

源码分析

整体概述

ArrayList继承AbstractList类,实现了List接口,底层是基于数组实现的,也意味着具有与数组类似的优缺点。它可以根据存储元素数量动态的调整容量大小,并且允许null值的存在。同时,实现了RandomAccess,Cloneable,Serializable接口,所以ArrayList支持快速访问,复制和序列化等操作。


成员变量
/**
 * 默认的初始容量
 */
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
/**
 * 集合中实际存放的元素数量。默认为 0
 */
private int size;
/**
 * 默认的最大能扩容的容量 :2^31-1 减去 8
 * 并不是实际最大扩容值。实际值为 Integer.MAX_VALUE
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法
public ArrayList()

无参构造函数。成员变量DEFAULTCAPACITY_EMPTY_ELEMENTDATA直接赋值给elementData

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c)

参数为Collection类型的构造函数。通过判断传入列表是否为空,来执行操作:为空则将成员变量EMPTY_ELEMENTDATA直接赋值给elementData,不为空则拷贝数据到ArrayListelementData中。

public ArrayList(Collection<? extends E> c) {
    // 参数 c 转换为 Object[] 并赋值给 elementData
    elementData = c.toArray();
    // 判断数组长度不为0,则拷贝 传入集合 到 elementData
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 传入的集合为空,成员变量 EMPTY_ELEMENTDATA 直接赋值给 elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
public ArrayList(int initialCapacity)

参数为int型的构造函数。判断initialCapacity是否大于 0 :

  • 大于 0 ,创建长度为initialCapacity的数组并赋值给elementData
  • 等于 0 ,成员变量EMPTY_ELEMENTDATA直接赋值给elementData
  • 小于 0 ,直接抛出异常
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

添加元素及扩容机制分析
添加元素方法

ArrayList中一共有四个add方法:

  • public boolean add(E e)
public boolean add(E e) {
    // 扩容逻辑相关
    ensureCapacityInternal(size + 1);
    // 将新元素添加到elementData数组中
    elementData[size++] = e;
    return true;
}
  • public void add(int index, E element)
public void add(int index, E element) {
    // 判断传入索引位置是否符合要求
    rangeCheckForAdd(index);
    // 扩容逻辑相关
    ensureCapacityInternal(size + 1);
    /*
    * 将 index下标System.arraycopy 方法时浅拷贝及其之后的元素整体后移
    * 注意这里 System.arraycopy 方法时浅拷贝
    */
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 插入值
    elementData[index] = element;
    size++;
}
  • public boolean addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
    // 集合转换为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    // 扩容逻辑相关
    ensureCapacityInternal(size + numNew);  
    // 浅拷贝 a 数据值到 成员变量 elementData 中
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
  • public boolean addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
    // 判断传入索引位置是否符合要求
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    int numNew = a.length;
    // 扩容机制相关
    ensureCapacityInternal(size + numNew);
    // 计算数据后移量
    int numMoved = size - index;
    // 大于 0 ,元素后移,以便在指定索引处插入新元素
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}
扩容机制

从上面的add(....)方法可以看出,添加数据过程其实就是列表动态扩容和数组拷贝操作的结合,所以动态扩容是保证ArrayList正常使用最关键的部分。

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

上述是add方法方法中扩容相关的ensureCapacityInternal(...)方法源码。调用了ensureExplicitCapacity(...)并嵌套调用了calculateCapacity(...)。先来看calculateCapacity(...)方法:

参数为 elementDataminCapacity。浏览参数值的传递过程可知:elementData取值就是成员变量elementDataminCapacity为执行添加操作后列表的元素实际数量值。

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

首先判断elementData是否是空对象。若是则为第一次添加元素,判断minCapacityDEFAULT_CAPACITY(10),返回较大值。这避免了当元素数量小于10时,频繁的扩容操作,减少了内存消耗。否则直接返回元素数量。这个返回值将用于判断是否执行扩容操作。

在获取到fcalculateCapacity(...)的返回值后,接着调用ensureExplicitCapacity(...)

参数为minCapacity,取值为执行添加操作后的实际元素数量值

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

添加操作后的元素数量值与元素数组长度值相减。小于 0 则表示当前的数组长度过段短,不够存放所有元素,需要执行扩容操作。

private void grow(int minCapacity) {
    // 原数组长度
    int oldCapacity = elementData.length;
    // 新数组长度:oldCapacity + oldCapacity / 2 。扩容为原数组长度的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 判断新数组长度是否满足存放添加操作后的所有元素
    if (newCapacity - minCapacity < 0)
        // 不满足则新数组长度等于添加操作后的元素数量
        newCapacity = minCapacity;
    // 判断新数组长度是否大于默认的容量最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 若是,从新获取新数组长度
        newCapacity = hugeCapacity(minCapacity);
    // 创建新数组长度的数组,拷贝 elementData 元素到新数组,并将新数组赋值给 elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
* 当添加操作后的实际元素数量大于默认容量最大值时调用。
* 最终返回值最大可能为 Integer.MAX_VALUE。
* ArrayList 的容量,最大扩容到 Integer.MAX_VALUE
*/
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

以上就是ArrayList的整个扩容过程。最终扩容的容量不一定就是原长度的 1.5 倍,也可能更大。

其他公共方法
clear()

清空整个列表。遍历elementData,并全部置空。size归 0 。

public void clear() {
    modCount++;
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
clone()

拷贝一个新的ArrayList。根据源码,最终调用的是System.arraycopy(...)。该方法执行的是浅拷贝。新旧 List 在针对对象操作时会互相影响。

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
contains(Object o) 和 indexOf(Object o)

public boolean contains(Object o):判断列表中是否存在对象o。调用了indexOf(Object o)

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

public int indexOf(Object o):获取列表中参数o的索引位置。遍历全部列表数据,返回符合要求的索引下标

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
ensureCapacity(int minCapacity)

调用该方法,可列表底层数组进行扩容。在预知到有多少数据量的情况下,提前对底层数组进行扩容,这样减去了在添加过程中一次次扩容的性能消耗。合适的时间调用可以有效提高程序性能。

// 参数 minCapacity 为最小数据量
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
size()

获取到的是列表中实际的元素数量

public int size() {
    return size;
}
isEmpty()

判断列表是否为空。

public boolean isEmpty() {
    return size == 0;
}
sort(Comparator<? super E> c)

排序方法。传入自定义的 Comparator实现类,来确定排序逻辑。

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
toArray(…)

将List转化为数组对象。有两个方法。

// 返回一个 Object 数组
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

// 返回一个 指定类型的数组
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

主要注意点

  • 底层是数组,根据数组大小和实际元素数量的对比,来进行动态调整
  • 数组默认有初始容量,值为 10。避免前期添加元素数量小于 10 时,频繁进行扩容操作
  • 理论扩容机制,执行扩容后,新容量是旧容量的 1.5 倍。但实际过程中,若扩容后的容量依然小于当前元素最小数量值,则直接取最小数量值为新的容量,最终的扩容倍数要大于 1.5。
  • 定义的最大元素数量为 Integer.MAX_VALUE - 8,即 (2^31-1) - 8。但在扩容过程中,若最小元素数量值大于了这个值,则取 Integer.MAX_VALUE 为最大容量。也就是说,ArrayList 最终能扩容到的最大容量为 Integer.MAX_VALUE。
  • 调用 clone 获得的新 ArrayList,若修改任意集合中引用类型的元素,新旧列表会互相影响。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值