ArrayList特点分析及源码阅读

2023-10-26

1、特点:

ArrayList是个动态数组,实现List接口,主要用来存储数据,如果存储基本类型的数据,如int,long,boolean,short,byte,那只存储它们对应的包装类。

增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。

查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

ArrayList 是 Java 集合框架中的成员之一,底层是基于数组实现,并且集合容量可动态变化的。它继承自 AbstractList 抽象类,实现了 List 接口。同时还实现了 RandomAccess,Cloneable 和 Serializable 三个标记接口,说明 ArrayList 是支持快速随机访问,可克隆复制的,可序列化的。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}

 

 

2、ArrayList线程安全吗?

ArrayList是线程不安全的。

当开启多个线程操作List集合,向ArrayList中增加元素,同时去除元素。最后输出list中的所有数据,会出现几种情况:

① 有些元素输出为Null;

② 数组下标越界异常。

为什么ArrayList线程不安全,我们还使用它?

因为大多数的场景中,查询操作使用频率高,增删操作的使用频率低。如果涉及频繁的增删,可以使用LinkedList,实际开发过程中还是ArrayList使用最多的。 不存在一个集合既查询效率高,又增删效率高,还线程安全的,因为数据结构的特性就是优劣共存的,想找个平衡点很难,牺牲了性能,那就安全,牺牲了安全那就快速。

3、ArrayList源码分析

3.1 变量

ArrayList 底层是基于数组实现,并且集合容量可动态变化。类中定义了一个 Object 类型的数组存储元素,因为是 Object 类型的数组,所以也只能存储引用数据类型,如果存储基本数据类型(例如 int ,double等),底层会自动将基本数据类型进行类的包装。

ArrayList 中还定义了一个记录数组元素个数的变量,以及一个代表默认初始化容量的静态变量。

/**
 * ArrayList 中存储元素的数组,数组的长度即集合的容量
 * 不用 private 关键字修饰是为了内部类方便访问此数组
 * transient 关键字修饰代表此数组不作为序列化的一部分
 */
transient Object[] elementData;
/**
 * 记录 ArrayList 集合中实际存储的元素个数
 */
private int size;
/**
 * 默认初始化容量
 */
private static final int DEFAULT_CAPACITY = 10;

ArrayList 中定义了两个静态空数组对象,主要用来当集合初始化,并且没有添加任何元素时,作为一个空数组对象赋值给 elementData 数组对象,代表一个空 list。那为啥要定义2个空数组对象呢?后面会细讲。

/**
 * 一个共享的静态空数组对象,用于空 list 集合中
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
 * 一个共享的静态空数组对象,用于默认大小初始化的空 list 集合中
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList 的父类 AbstractList 中定义了 modCount 变量,它记录了集合被操作修改的次数,在使用迭代器 Iterator 中会被使用到,因为在使用迭代器遍历集合元素的过程中集合结构不允许被修改(例如添加元素,删除元素),通过比较 modCount 的值能防止在迭代的过程中集合被修改。

protected transient int modCount = 0;

3.2 构造函数

ArrayList 有三个构造函数,一个无参构造函数,一个指定集合容量大小的有参构造函数,以及一个使用指定 Collection 集合来构造集合的构造函数。

无参构造函数,即初始化一个容量为10的空 list 集合,虽说是初始化容量为10的集合,但是实际此时没创建一个容量为10的数组,而只是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个空数组对象赋值给 elementData 变量,只有在第一次添加元素时才创建一个容量的为10的数组。

/**
 * 构造一个容量为10的空 list
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

指定初始化容量的有参构造函数,当初始化容量大于0时,直接创建指定容量大小的数组,如果初始化容量为0,则将 EMPTY_ELEMENTDATA 空数组对象赋值给 elementData 变量。

/**
 * 构造一个指定初始化容量大小的空 list
 */
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);
    }

使用指定 Collection 集合来构造 ArrayList 对象,如果 Collection 不为空,则将其元素拷贝赋值到 elementData 中,如果 Collection 为空,则还是创建一个空的 list,相当于 new ArrayList(0)。

/**
 * 构造一个 list,它包含了指定 Collection 集合元素,这些元素的顺序是通过集合迭代器返回元素的顺序决定的
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3.3 常用方法(增删改查)

1、增

public boolean add(E e)

在 list 尾部添加一个元素,首先会将记录对集合操作的次数加1,然后再判断集合容量是否足够,不够则进行扩容。

/**
 * 在 list 尾部添加一个元素
 */
public boolean add(E e) {
    // 修改操作次数,并且是否进行扩容操作
    ensureCapacityInternal(size + 1);
    // 将新元素添加在数组尾部
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    // 如果是采用默认初始化容量10构造的 list,则取默认初始化容量10和当前需要添加元素的下标+1的最大值来初始化 list
    // 这里就说明了为什么要定义2个空数组对象,因为通过判断空 list 是否等于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
    // 如果是,则使用默认的初始化容量10来进行扩容
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    // 修改次数加1
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        // 扩容
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 扩容到原来容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 最大容量为 Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 拷贝数组
    elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
//参数为所需要的最小容积值
    private int newCapacity(int minCapacity) {
        int oldCapacity = elementData.length;  获取原始数组的容积值
        int newCapacity = oldCapacity + (oldCapacity >> 1);   新容积值=原始容积值+原始容积值/2  扩容50%  【重点】
        if (newCapacity - minCapacity <= 0) {  newCapacity新容积值 是否小于等于 所需要的最小容积值minCapacity
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 当前存放数据的数组是否为默认的空数组
                return Math.max(DEFAULT_CAPACITY, minCapacity);  获取默认容积10和所需要的最小容积1的较大值
            if (minCapacity < 0) 判断所需要的最小容积值是否小于0,如果小于0则一定是越界  size+1超出int范围
                throw new OutOfMemoryError();  抛出OOM
            return minCapacity;  返回所需要的最小容积值
        }
        //newCapacity新容积值 大于 所需要的最小容积值minCapacity
         * 这里的MAX_ARRAY_SIZE=Integer.MAX_VALUE-8
        return (newCapacity - MAX_ARRAY_SIZE <= 0) 新容积值如果小于MAX_ARRAY_SIZE则返回新容积值
            ? newCapacity
            : hugeCapacity(minCapacity);  如果所需要的最小容积值大于MAX_ARRAY_SIZE,则返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE
    }

在指定位置添加元素

public void add(int index, E element)

在 list 指定下标位置添加一个元素,首先会检查下标是否在范围内,将记录对集合操作的次数加1,然后再判断集合容量是否足够,不够则进行扩容。

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将下标位置后面的所有元素往后拷贝移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

将指定 Collection 的所有元素添加到 list 的尾部中去,还是先检查是否需要扩容,最后再将 Collection 集合拷贝到 list 尾部。

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);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

2、改

public E set(int index, E element)

修改 list 指定下标位置的元素,首先会检查下标是否在范围内,然后替换指定下标的元素,返回旧的元素。

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

3、返回集合中元素的个数

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

4、判断集合是否为空,即集合是否有元素

public boolean isEmpty()
/**
 * Returns <tt>true</tt> if this list contains no elements.
 *
 * @return <tt>true</tt> if this list contains no elements
 */
public boolean isEmpty() {
    return size == 0;
}

5、判断集合是否包含某元素,主要通过 equals 方法比较是否存在某元素。

public boolean contains(Object o)
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
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;
}

6、返回指定下标位置的元素,访问速度快。

public E get(int index)
public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

7、删

 public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
        	//判断所需要的删除元素如果为null,则直接使用==判断,非空则使用equals判断
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)  查找所需要删除的元素位置,找到后添加found:{}范围
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;  如果找不到则返回false
        }
        fastRemove(es, i);  参数1是存储数据的数组,i为查找到的对应位置下标
        return true;
    }

8.如何使用clone()方法

 public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("云");
        list.add("烟");
        list.add("成");
        list.add("雨");
        Object o = list.clone();
        System.out.println(o);
        System.out.println(list);
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ArrayList特点分析及源码阅读 的相关文章

  • 如何替换引号之间出现的任何单词

    我需要能够替换所有出现的单词 and 仅当它出现在单引号之间时 例如 将字符串中的 and 替换为 XXX This and that with you and me and others and not her and him 结果是 T
  • 如何将TabLayout与Recyclerview同步?

    我有一个TabLayout with Recyclerview这样当单击选项卡时Recyclerview滚动到特定位置 我也想要相反的过程 这样当Recyclerview滚动到特定位置 然后该特定选项卡会突出显示 例如 如果有 4 个选项卡
  • 在 IntelliJ 插件中创建后台任务

    我正在开发一个 IntelliJ idea 插件 并希望在后台任务中运行代码 在后台任务对话框和 UI 之外的另一个线程中可见 我发现了以下内容助手类 https github com inmite android selector cha
  • 添加样式后如何重置回默认CSS?

    基本上 我通过添加如下样式类来更改 javafx 中文本字段的 css textfield getStyleClass add textfieldstyle 但后来我希望能够将其恢复到原来的样子 但由于本例中的原始外观是 JavaFX 的默
  • 具有多字符替换的字符串组合(产生返回Java的替代重写)[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 还有另一篇 Stack Overflow 帖子是为与车辆登记号相关的算法创建的 根据输入的车牌 例如ABC123 和列表 替换值 例如
  • Spring Security“拒绝执行来自...的脚本”

    我正在 HTML 文件 thymeleaf 模板 中使用 Spring Security 和 Bootstrap 构建 Spring MVC 应用程序 Spring Security部分基于Spring Guide对于春季安全 http s
  • 从 Java 监听系统鼠标点击

    我的主要目的是计算特定应用程序上的鼠标点击次数 想象一下 我在 PC 上打开了 Microsoft Word 和 Web 浏览器 我的 Java 代码应该告诉我单击 Word 和 Web 浏览器的次数 我需要应用程序名称和点击次数 我怎样才
  • WAR 文件在 Tomcat 服务器中抛出 OutOfMemoryError

    我有一个 Spring MVC WAR 文件 可以在我的本地计算机 程序和网站 中完美运行 一旦我将文件上传到服务器 aTomcat 7 并尝试访问它 catalina 日志文件表明java lang OutOfMemoryError 我尝
  • 没有 if 条件(动态查询)或乱码的Where子句中的PreparedStatement“为null”

    假设我有这样的查询 SELECT FROM CUSTOMERS WHERE CUSTOMER ID 使用PreparedStatement 我可以绑定变量 pstmt setString 1 custID 但是 我无法通过以下绑定获得正确的
  • 将分区扩展到另一级

    根据下图来自春季批量文档 http docs spring io spring batch reference html scalability html partitioning 主步骤被划分为六个从步骤 它们是主步骤的相同副本 我的问题
  • 将一组 Java 对象转换为另一组对象的最佳方式是什么?

    这是一个真正的新手提出的基本 Java 问题 我有一组实现某个接口 接口 MyIfc 的Java对象 属于 MyClass 类 我有一组这些对象存储在我的类中的私有变量中 声明如下 protected Set
  • Spring MVC - 从 JSP 提交对象

    我有一个显示客户列表的 JSP ArrayList searchResults 我希望能够选择其中之一 并将其 提交给 Spring MVC 控制器 但是 我似乎无法传递所选对象 只能传递它的属性 例如 customerId 我真的需要传递
  • 如何在 Spring Boot 中跳过将某些 @Entity 类创建为 h2(内存中)数据库中的表?

    我正在尝试构建一个使用 2 个数据源的 Spring Boot 应用程序 我现在的主要数据库是内存数据库 仅用于测试目的 其中的表是在我创建的 sql 文件的帮助下填充的 另一个数据库 oracledb 具有已填充的表 我想实现什么目标 我
  • 在 Eclipse 中编写链接特定行的注释

    我正在 Java 中使用 Eclipse 并且处理很长的类 我需要这样的功能 在方法的顶部注释中 例如 有一个由该方法执行的操作列表 对于列出的每个操作 我想将注释的一部分 超链接 到相关代码的特定行 然后使用 Ctrl Click 到该行
  • Java如何处理IF语句和效率

    我只是好奇 Java 实际是如何工作的if声明 注意 当我在下面说 组件 时 我指的是语句检查的各个部分 例如a b c 哪个在计算方面更有效 if a b c do stuff or if a if b if c do stuff 我之所
  • 从 MySql 迁移:MariaDB 服务器意外关闭客户端连接

    由于许可 商业使用原因 我们正在从 MySql 迁移到 MariaDB 我们已经成功用 MariaDB 客户端 jar 替换了 MySql 连接器 jar 第一次更改 现在正在尝试用 MariaDB 服务器替换 MySql 服务器而不更改数
  • 序列化/反序列化 LinkedHashMap (android) java

    所以我想将 LinkedHashMap 传递给意图 SEND THE MAP Intent singlechannel new Intent getBaseContext singlechannel class singlechannel
  • 如何处理MaxUploadSizeExceededException

    MaxUploadSizeExceededException当我上传的文件大小超过允许的最大值时 会出现异常 我想在出现此异常时显示错误消息 如验证错误消息 我该如何处理这个异常 以便在 Spring 3 中执行类似的操作 Thanks 这
  • 优化Gson反序列化

    优化反序列化的最佳方法是什么 我目前正在使用标准 Gson toJson 和 Gson fromJson 方法来序列化和反序列化一些复杂对象 我希望尽可能减少反序列化时间 如果重要的话 我的最复杂的对象包含 43 个变量 如果你想使用 Gs
  • 使用java读取行并映射过滤数据[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions publi

随机推荐

  • PID串行多闭环控制与并行多闭环控制的优缺点分析和应用比较

    导言 在自动控制领域 PID控制器是一种经典的控制策略 被广泛应用于各种工业和非工业过程 随着控制系统的复杂性增加 PID串行多闭环控制和PID并行多闭环控制成为解决复杂控制问题的重要方法 本文将从优点和缺点的角度对这两种控制策略进行对比
  • Android基础之Fragment

    目录 前言 一 Fragment简介 二 Fragment的基础使用 1 创建Fragment 2 在Activity中加入Fragment 1 在Activity的layout xml布局文件中静态添加 2 在Activity的 java
  • 数学建模--粒子群算法(PSO)的Python实现

    目录 1 开篇提示 2 算法流程简介 3 算法核心代码 4 算法效果展示 1 开篇提示 开篇提示 这篇文章是一篇学习文章 思路和参考来自 https blog csdn net weixin 42051846 article details
  • 宝峰对讲机16频率表_宝峰888S对讲机的16个信道频率是多少?

    1 宝峰888S对讲机 16个工作频率范围为 400 470MHZ 16个信道 频率范围内 任意频道任意频率 内 2 一般对讲机没容有固定频点 出厂都是空频机器 每个信道的频率都可以写成机器频率范围内的任意频点也可以空白什么都不写 3 根据
  • 矩阵求逆四种方法

    注 用A B表示某矩阵 E表示单位矩阵 用A 表示A逆 用 A 表示A的行列式 A E 表示拼接矩阵 一 公式法 先求A行列式结果 再求A伴随矩阵 最后再求A逆矩阵 A 0 则 A A A 注 图片中detA就是 A 二 初等变换法 A E
  • 【沧海拾昧】Proteus8仿真stm32:ADC转换程序

    C0102 沧海茫茫千钟粟 且拾吾昧一微尘 沧海拾昧集 CuPhoenix 阅前敬告 沧海拾昧集仅做个人学习笔记之用 所述内容不专业不严谨不成体系 如有问题必是本集记录有谬 切勿深究 目录 一 原理图绘制 二 多位七段数码管 三 ADC引脚
  • 一维动态规划总结

    题目列表 给一个N 输入 求某种情况的最大值或者最小值情况 279 Perfect Squares 思路 最差情况下 总体是定义一个dp N 1 或者初始化前面dp 0 或者dp 1 279 Perfect Squares 解析 Given
  • sql:command not found

    写一个脚本zl sh 用来删除数据库mydatabase中某个表mytable的某行数据 bin bash HOSTNAME 127 0 0 1 PORT 2918 USERNAME root PASSWORD root TABLENAME
  • 使用mockjs创建假数据

    npm install mockjs 创建mock文件夹 在mock文件夹下创建1 js 1 js import Mock from mockjs 引入mockjs export default Mock mock postdata1 po
  • 剑网三服务器缺少必要启动文件,win7系统玩剑网三游戏经常掉线的解决方法

    很多小伙伴都遇到过win7系统玩剑网三游戏经常掉线的困惑吧 一些朋友看过网上零散的win7系统玩剑网三游戏经常掉线的处理方法 并没有完完全全明白win7系统玩剑网三游戏经常掉线是如何解决的 今天小编准备了简单的解决办法 只需要按照1 掉线基
  • 循环神经网络RNN以及几种经典模型

    RNN简介 现实世界中 很多元素都是相互连接的 比如室外的温度是随着气候的变化而周期性的变化的 我们的语言也需要通过上下文的关系来确认所表达的含义 但是机器要做到这一步就相当得难了 因此 就有了现在的循环神经网络 他的本质是 拥有记忆的能力
  • el-menu-item内容过多,不能滚动

    问题描述 这里放了六张图片 只能看到最下面的部分 上面的部分被挤出了屏幕外面 这里的弹出框是element ui组件自动生成的 即这个div 我此时有关这部分的代码如下 解决思路 一开始是想抓住这个生成的div 修改这个div的样式试图让它
  • python 2.x安装

    1 查看当前python版本 python version 2 安装最新2 x版本 brew install python 2 安装完成后 注意一下提示 pip and setuptools have been installed To u
  • 阻碍区块链应用落地的五大难题和解决方案

    2018年初区块链掀起了一阵新热潮 多家互联网公司纷纷宣布推出区块链项目 新兴的区块链项目方和媒体百家争鸣 一时之间区块链行业风光无限 区块链概念的火爆 使得越来越多的人开始学习它 理解它 甚至 拥抱 它 只是沉浸在 狂欢 里的众人怎么也没
  • show,attend and tell(image caption论文复现总结)

    论文中的核心思想 GitHub上的Image Caption项目https github com sgrvinod a PyTorch Tutorial to Image Captioning 研究的问题 Image Caption 为图片
  • _I,_O,_IO,条件编译#ifndf _HEAD_H中的下划线_是什么,有什么用

    1 其实质是一个宏名 由此我们可以防止发生重复定义或声明 2 编程风格 使标识符含义更清晰易懂 假设你的头文件名为head h 根据习惯 我们声明一个宏HEAD H 对应这个头文件 在头文件中开始的地方和结尾的地方加上 对HEAD H的声明
  • CentOS8更换阿里云yum源

    以下是使用阿里云的CentOS 8镜像源配置文件作为示例 备份原有的yum源文件 以便需要时恢复 sudo mv etc yum repos d CentOS tmp 下载并安装阿里云的CentOS 8源配置文件 sudo curl o e
  • AJAX JSON的数据传输

    文章目录 AJAX的JSON引入 javascriptJSON对象创建和访问 javascript怎么创建JSON对象 javascript访问JSON javascript字符串转换成JSON对象 对案例进行改造 使用json传输 将ja
  • spring boot 使用审计

    创建User类 测试类只有一个name属性 Entity Table name UcenterUser Data public class User extends BaseEntity private String name 抽取一个基类
  • ArrayList特点分析及源码阅读

    1 特点 ArrayList是个动态数组 实现List接口 主要用来存储数据 如果存储基本类型的数据 如int long boolean short byte 那只存储它们对应的包装类 增删慢 每次删除元素 都需要更改数组长度 拷贝以及移动