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激活码批量导入最新pycharm破解配置

    本教程适用于IDEA、PyCharm、DataGrip、Goland等,支持Jetbrains全家桶! 话不多说,先放一张成功把 PyCharm 续命到 2099 年的截图镇楼——看到 License 有效期直接拉满,爽翻! 下面我就用图文结合的方式,手把手带你把 PyCharm 一口气激活到 2099 年。注意:这套方法对老版本同样有效,Windows /…

    PyCharm激活码 2025 年 11 月 8 日
    16800
  • 无需账号注册goland激活码,权威破解教程一站式服务

    免责声明:以下激活补丁与授权码均源自互联网公开资源,仅供个人学习研究,禁止商业用途。若条件允许,请支持正版! 先放一张实测截图:GoLand 2025.2.1 已成功激活到 2099 年,爽到飞起! 下面用图文手把手演示如何给最新版 GoLand 打上“长寿”补丁。 前期准备 ⚠️ 如果你之前尝试过其他破解方案失败,建议彻底卸载并重新安装,或手动清理旧配置。…

    2025 年 11 月 10 日
    16900
  • IDEA激活码2025免费获取|IDEA破解码一键激活实测有效!

    本教程适用于IDEA、PyCharm、DataGrip、Goland等,支持Jetbrains全家桶! 废话不多说,先上最新 IDEA 版本破解成功的截图,如下,可以看到已经成功破解到 2099 年辣,舒服! 接下来,我就将通过图文的方式, 来详细讲解如何激活 IDEA至 2099 年。 当然这个激活方法,同样适用于之前的旧版本! 不管你是什么操作系统,什么…

    2025 年 9 月 30 日
    33200
  • 最新IDEA破解工具教程|激活码+破解码+永久使用指南

    本教程适用于 IDEA、PyCharm、DataGrip、Goland 等,支持 Jetbrains 全家桶! 废话不多说,先上最新 IDEA 版本破解成功的截图,如下,可以看到已经成功破解到 2099 年辣,舒服! 接下来,我就将通过图文的方式,来详细讲解如何激活 IDEA 至 2099 年。当然这个激活方法,同样适用于之前的旧版本! 不管你是什么操作系统…

    IDEA破解教程 2025 年 9 月 20 日
    1.1K00
  • IDEA破解方式更新,激活码同步可用

    本教程适用于 IDEA、PyCharm、DataGrip、Goland 等,支持 Jetbrains 全家桶! 废话不多说,先上最新 IDEA 版本破解成功的截图,如下,可以看到已经成功破解到 2099 年辣,舒服! 接下来,我就将通过图文的方式,来详细讲解如何激活 IDEA 至 2099 年。当然这个激活方法,同样适用于之前的旧版本!不管你是什么操作系统,…

    IDEA破解教程 2025 年 11 月 30 日
    22600

发表回复

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

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

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

关注微信