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
上一篇 7小时前
下一篇 4小时前

相关推荐

  • 🚀 2025最新PyCharm永久激活教程|破解2099年|亲测有效(附激活码)

    还在为PyCharm的激活问题发愁吗?😫 本教程将手把手教你如何永久激活PyCharm至2099年!适用于所有Jetbrains全家桶(IDEA、PyCharm、DataGrip、Goland等),无论你是Windows、Mac还是Linux系统,统统100%成功!💯 🎯 效果预览 先来看看最新PyCharm版本破解成功的截图,已经完美激活到2099年啦!🎉…

    2025 年 5 月 13 日
    25200
  • 2025年最新DataGrip永久破解教程(附激活码/注册码)

    本教程适用于JetBrains全家桶(IDEA/PyCharm/DataGrip/Goland等) 先看破解成果!成功激活至2099年,亲测可用! 下面将详细介绍DataGrip的完整破解流程,该方法同样适用于旧版本,支持全平台操作系统。 下载DataGrip官方安装包 已安装用户可跳过此步骤 访问官网下载:https://www.jetbrains.com…

    2025 年 5 月 8 日
    25500
  • PostgreSQL 的特点

    title: PostgreSQL 的特点date: 2024/12/24updated: 2024/12/24author: cmdragon excerpt:PostgreSQL 是当今最流行的开源关系型数据库之一,凭借其优秀的性能、稳定性和丰富的功能集在用户群中享有极高声誉。相比于其他关系型数据库管理系统,PostgreSQL 拥有许多独特的特点,使其…

    2024 年 12 月 30 日
    27400
  • JavaScript 拖拽与观察者模式的实现及应用

    前言 本文将通过几个具体的代码片段,深入探讨 JavaScript 中的拖拽功能和观察者模式(发布-订阅模式)的实现及其应用场景。 这些代码片段不仅展示了如何实现这些功能,还解释了其背后的原理和实际用途。通过阅读本文,读者可以更好地理解 JavaScript 的高级特性,并将其应用到实际项目中。 1. 拖拽功能的实现 代码片段 “`html Documen…

    2025 年 1 月 19 日
    37800
  • IDEA永久破解,IDEA最新激活码,快速激活2024版IDEA

    本教程适用于IDEA、PyCharm、DataGrip、Goland等,支持Jetbrains全家桶! 废话不多说,先给大家看一下最新IDEA版本的破解截图,可以看到已经成功破解至2099年,激活效果非常好! 接下来,我会通过图文方式,详细讲解如何激活IDEA至2099年。 无论你使用的是Windows、Mac还是Linux系统,无论你的IDEA版本是多少,…

    PyCharm破解教程 2025 年 4 月 12 日
    41000

发表回复

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

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

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

关注微信