Java SDK源码剖析之ArrayList篇章(第四部分)

文章标题:

Java SDK源码解读之ArrayList相关内容(第四部分)

文章内容:

目录

  • 1. 定义
  • 2. 使用方式
  • 3. 原理探究
    • 3.1. uml关系
    • 3.2. 构造机制
    • 3.3. 添加元素方法
    • 3.3.1. 保障容量满足新增需求
    • 3.3.2. 将元素放置到数组末尾位置
    • 3.4. 按索引删除元素方法
    • 3.4.1. 将索引位置之后的数组元素向前移动
    • 3.4.2. 更新元素数量(数组不自动缩容)
    • 3.5. 按元素内容删除方法
    • 3.5.1. 首先定位要删除元素的索引
    • 3.5.2. 将索引位置之后的数组元素向前移动
    • 3.5.3. 更新元素数量(数组不自动缩容)
  • 4. ConcurrentModificationException相关
    • 4.1. 原因剖析
    • 4.2. 解决办法

1. 定义

它是基于数组实现的、能够动态扩容的顺序存储结构,具有有序特性且允许元素重复

2. 使用方式

public class ArrayListDemo
{
    public static void main(String[] args)
    {
        ArrayList<Integer> listInstance = new ArrayList<>();
        listInstance.add(1);
        listInstance.add(2);

        System.out.println(listInstance);

        listInstance.remove(0);
        listInstance.remove(Integer.valueOf(2));

    }
}

3. 原理探究

3.1. uml关系

可以看出ArrayList实现了List接口,具备克隆和序列化能力,同时支持通过索引快速访问元素

3.2. 构造机制

采用Object类型的数组作为底层存储,初始长度为0

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //默认初始容量为10,当使用无参构造时初始容量为0,首次扩容会达到10
    private static final int DEFAULT_CAPACITY = 10;

    //无元素时使用的空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //底层存储元素的Object数组
    transient Object[] elementData; 

    //数组中实际元素的数量
    private int size;

    public ArrayList() {
        //默认使用空数组初始化
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
}

3.3. 添加元素方法

  • 不扩容时时间复杂度为O(1),扩容时为O(N)
public boolean add(E element) {
    //确保容量足够容纳新元素
    ensureCapacityInternal(size + 1);  // 增加修改计数!!
    //将元素放置到数组末尾并更新元素数量
    elementData[size++] = element;
    return true;
}
3.3.1. 保障容量满足新增需求
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //使用无参构造创建的实例初始容量为0,首次扩容为10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    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)
        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;
}
3.3.2. 将元素放置到数组末尾位置
//简单赋值并更新元素数量
elementData[size++] = element;

3.4. 按索引删除元素方法

  • 时间复杂度为O(N)
public E remove(int index) {
    //检查索引有效性
    rangeCheck(index);

    modCount++;
    //保存要删除的元素以便返回
    E removedValue = elementData(index);

    //计算需要移动的元素数量
    int movedCount = size - index - 1;
    if (movedCount > 0)
        //将后面的元素向前移动
        System.arraycopy(elementData, index+1, elementData, index,
                         movedCount);
    //数组实际大小未变,但需更新元素数量并清理原位置
    elementData[--size] = null; // 让GC回收空间

    return removedValue;
}
3.4.1. 将索引位置之后的数组元素向前移动
//计算需要移动的元素个数
int movedCount = size - index - 1;
if (movedCount > 0)
    //将后面的元素复制到前面
    System.arraycopy(elementData, index+1, elementData, index,
                     movedCount);
3.4.2. 更新元素数量(数组不自动缩容)
//数组大小未实际改变,但需更新元素数量并清理原位置
elementData[--size] = null; // 让GC回收空间

3.5. 按元素内容删除方法

  • 时间复杂度为O(N)
public boolean remove(Object target) {
    //处理目标为null的情况
    if (target == null) {
        for (int idx = 0; idx < size; idx++) {
            if (elementData[idx] == null) {
                fastRemove(idx);
                return true;
            }
        }
    } else {
        for (int idx = 0; idx < size; idx++) {
            if (target.equals(elementData[idx])) {
                fastRemove(idx);
                return true;
            }
        }
    }
    return false;
}
3.5.1. 首先定位要删除元素的索引
for (int idx = 0; idx < size; idx++)
{
//...
}

上述代码主要是遍历查找目标元素的索引,时间复杂度为O(N),找到索引后执行删除操作,如下:

快速删除方法
private void fastRemove(int index) {
    modCount++;
    //计算需要移动的元素数量
    int movedCount = size - index - 1;
    if (movedCount > 0)
        //将后面的元素向前移动
        System.arraycopy(elementData, index+1, elementData, index,
                         movedCount);
    //更新元素数量并清理原位置
    elementData[--size] = null; // 让GC回收空间
}

该方法逻辑与按索引删除元素的逻辑类似

3.5.2. 将索引位置之后的数组元素向前移动
//计算需要移动的元素个数
int movedCount = size - index - 1;
if (movedCount > 0)
    //将后面的元素复制到前面
    System.arraycopy(elementData, index+1, elementData, index,
                     movedCount);
3.5.3. 更新元素数量(数组不自动缩容)
//更新元素数量并清理原位置
elementData[--size] = null; // 让GC回收空间

4. ConcurrentModificationException相关

参考:fail-fast.md

public class ConcurrentModificationDemo
{
    public static void main(String[] args)
    {
        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 1000; i++)
        {
            stringList.add(String.valueOf(i));
        }

        for (String item : stringList)
        {
            stringList.remove(item);//会抛出java.util.ConcurrentModificationException异常
        }
    }
}

上述代码运行时会抛出ConcurrentModificationException异常

4.1. 原因剖析

在遍历(增强for循环本质是迭代器遍历)过程中执行删除操作会触发快速失败机制,这是由于modCount和expectedCount不一致导致的

4.2. 解决办法

使用JUC包下支持fail-safe机制的CopyOnWriteArrayList

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/12889.html

(0)
LomuLomu
上一篇 2025 年 7 月 19 日
下一篇 2025 年 7 月 19 日

相关推荐

  • PyCharm 2026激活破解后如何防止失效

    本指南适用于IDEA、PyCharm、DataGrip、Goland等Jetbrains全家桶产品! 无需多言,先展示最新版PyCharm成功激活的截图。如下图所示,软件已成功授权至2099年,非常便捷! 接下来,我将通过详细的图文步骤,为大家讲解如何将PyCharm激活至2099年。 此激活方法同样适用于过往的旧版本! 无论您使用的是Windows、Mac…

    PyCharm激活码 2026 年 5 月 28 日
    15400
  • ChatGPT Pro代充无需国外信用卡方法

    如果你担心充值到错误账号,先确认当前登录邮箱和 Session,再继续后面的卡密验证。这篇主要回答「ChatGPT Pro代充无需国外信用卡方法」这个具体需求,适合想用会员功能处理优化简历和面试回答的用户。不要把不同账号的信息混用,Visa 或账单地址经常让新手卡住,所以建议先确认自己的账号和Session,再进入充值流程。

    未分类 2026 年 7 月 29 日
    3200
  • IDEA激活码获取新方式,不用破解也能永久免费用!

    声明:以下 IntelliJ IDEA 破解补丁与激活码均搜集自互联网,仅供个人学习参考,禁止商业用途。如有版权争议,请联系删除。若条件允许,请支持正版! JetBrains 出品的 IntelliJ IDEA 是一款跨平台(Windows / macOS / Linux)全能 IDE。本文手把手教你用最新补丁实现永久激活,解锁全部高级特性。 无论你用的是哪…

    IDEA破解教程 2025 年 9 月 21 日
    42300
  • MySQL函数深度探索:聚合、日期及字符串函数全面解析

    MySQL函数深度剖析:聚合、日期与字符串函数详解 文章目录 MySQL系列 引言 一、聚合函数 二、日期函数 三、字符串函数 四、数学函数 五、其他函数 引言 MySQL具备众多内置函数,可用于数据处理、计算执行以及格式转换等操作,本篇将会阐述MySQL中常用的一些函数。本文内容以实践为主 此处的函数较为基础,不再展开解释,若再赘述便有牵强之感 上篇文章:…

    2025 年 9 月 14 日
    34100
  • 最新pycharm破解激活步骤+永久激活码绑定

    申明:本教程Pycharm 破解补丁、激活码均收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除。若条件允许,希望大家购买正版 ! 废话不多说,先上 Pycharm2025.2.1 版本破解成功的截图,如下图,可以看到已经成功破解到 2099 年辣,舒服的很! 接下来就给大家通过图文的方式分享一下如何破解最新的Pycharm。 如果觉得破解麻烦…

    PyCharm激活码 2025 年 12 月 16 日
    33400

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信