Java集合篇:ArrayList详解

2023-10-28

一、ArrayList概述:

ArrayList是实现了List接口的动态数组,所谓动态数组就是他的大小是可变的。实现了所有可选列表操作,并允许包括Null在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量是10。默认初始容量为10。随着ArrayList中元素的增加,它的容量也会不断的自动增长。在每次添加元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时,可以给ArrayList 指定一个初始容量,这样就会减少扩容时的拷贝问题。当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

ArrayList 的实现不是同步的如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:

List list = Collections.synchronizedList(new ArrayList(...));

 

二、ArrayList源码分析:

ArrayList是实现List接口的,底层采用数组实现,所以它的操作基本上都是基于对数组的操作。

1、底层使用数组:

private transient Object[] elementData;

transient 为java关键字,为变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。 

这里Object[] elementData,就是我们的ArrayList容器,下面介绍的基本操作都是基于该elementData变量来进行操作的。

2、构造函数:

ArrayList提供了三个构造函数:

ArrayList():默认构造函数,提供初始容量为10的空列表。

ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。

ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

/**
     * 构造一个初始容量为 10 的空列表
     */
    public ArrayList() {
        this(10);
    }
    
    /**
     * 构造一个具有指定初始容量的空列表。
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "
                    + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     *  构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

3、新增:

 ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。

3.1 add(E e):将指定的元素添加到此列表的尾部。

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

这里ensureCapacity()方法是对ArrayList集合进行扩容操作,elementData(size++) = e,将列表末尾元素指向e。

 3.2 add(int index, E element):将指定的元素插入此列表中的指定位置。

public void add(int index, E element) {
        //判断索引位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);
        //扩容检测
        ensureCapacity(size+1);  
        /*
         * 对源数组进行复制处理(位移),从index + 1到size-index。
         * 主要目的就是空出index位置供数据插入,
         * 即向右移动当前位于该位置的元素以及所有后续元素。 
         */
        System.arraycopy(elementData, index, elementData, index + 1,
                 size - index);
        //在指定位置赋值
        elementData[index] = element;
        size++;
        }

在这个方法中最根本的方法就是System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,所以如果指定的数据集合需要进行大量插入(中间插入)操作,推荐使用LinkedList。

      3.3 addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。

public boolean addAll(Collection<? extends E> c) {
        // 将集合C转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        // 扩容处理,大小为size + numNew
        ensureCapacity(size + numNew); // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。这里就稍微介绍下System.arraycopy(),因为下面还将大量用到该方法。该方法的原型为:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是进行数组元素的复制。即从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。将源数组src从srcPos位置开始复制到dest数组中,复制长度为length,数据从dest的destPos位置开始粘贴。

3.4 addAll(int index, Collection<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {
        //判断位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
                    + size);
        //转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //ArrayList容器扩容处理
        ensureCapacity(size + numNew); // Increments modCount
        //ArrayList容器数组向右移动的位置
        int numMoved = size - index;
        //如果移动位置大于0,则将ArrayList容器的数据向右移动numMoved个位置,确保增加的数据能够增加
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                    numMoved);
        //添加数组
        System.arraycopy(a, 0, elementData, index, numNew);
        //容器容量变大
        size += numNew;   
        return numNew != 0;
    }

3.5 set(int index, E element):用指定的元素替代此列表中指定位置上的元素。

public E set(int index, E element) {
        //检测插入的位置是否越界
        RangeCheck(index);

        E oldValue = (E) elementData[index];
        //替代
        elementData[index] = element;
        return oldValue;
    }

4、删除:

ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四个方法进行元素的删除。

4.1 remove(int index):移除此列表中指定位置上的元素。

public E remove(int index) {
        //位置验证
        RangeCheck(index);

        modCount++;
        //需要删除的元素
        E oldValue = (E) elementData[index];   
        //向左移的位数
        int numMoved = size - index - 1;
        //若需要移动,则想左移动numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);
        //置空最后一个元素
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

4.2 remove(Object o):移除此列表中首次出现的指定元素(如果存在)。

public boolean remove(Object o) {
        //因为ArrayList中允许存在null,所以需要进行null判断
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //移除这个位置的元素
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

其中fastRemove()方法用于移除指定位置的元素。如下:

private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    }

4.3 removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。

protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System
                .arraycopy(elementData, toIndex, elementData, fromIndex,
                        numMoved);

        // Let gc do its work
        int newSize = size - (toIndex - fromIndex);
        while (size != newSize)
            elementData[--size] = null;
    }

4.4 removeAll():是继承自AbstractCollection的方法,ArrayList本身并没有提供实现。

public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        Iterator<?> e = iterator();
        while (e.hasNext()) {
            if (c.contains(e.next())) {
                e.remove();
                modified = true;
            }
        }
        return modified;
    }

5、查找:

ArrayList提供了get(int index)用读取ArrayList中的元素。由于ArrayList是动态数组,所以我们完全可以根据下标来获取ArrayList中的元素,而且速度还比较快,故ArrayList长于随机访问。

public E get(int index) {
        RangeCheck(index);

        return (E) elementData[index];
    }

6、扩容:

在上面的新增方法的源码中我们发现每个方法中都存在这个方法:ensureCapacity(),该方法就是ArrayList的扩容方法。在前面就提过ArrayList每次新增元素时都会需要进行容量检测判断,若新增元素后元素的个数会超过ArrayList的容量,就会进行扩容操作来满足新增元素的需求。所以当我们清楚知道业务数据量或者需要插入大量元素前,我可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

public void ensureCapacity(int minCapacity) {
        //修改计时器
        modCount++;
        //ArrayList容量大小
        int oldCapacity = elementData.length;
        /*
         * 若当前需要的长度大于当前数组的长度时,进行扩容操作
         */
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;
            //计算新的容量大小,为当前容量的1.5倍
            int newCapacity = (oldCapacity * 3) / 2 + 1;
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
            //数组拷贝,生成新的数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

在这里有一个疑问,为什么每次扩容处理会是1.5倍,而不是2.5、3、4倍呢?通过google查找,发现1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

处理这个ensureCapacity()这个扩容数组外,ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize()方法来实现。该方法可以最小化ArrayList实例的存储量。

public void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

 

文章转自:http://www.cnblogs.com/chenssy/p/3498468.html

 

 

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

Java集合篇:ArrayList详解 的相关文章

  • 安卓。 onEditorAction 从未被调用

    我正在尝试捕捉从屏幕上移除键盘的事件 并且我正在使用OnEditorActionListener班级 然而 其onEditorAction方法永远不会被调用 这是我的 XML 格式的 EditText
  • 如何使用 IO Codenameone 发布图片/图像

    因为 codenameone 不能使用外部库 HttpConnection 所以我必须使用 Codenameone 提供的内部库 API 只是我已经设法使用 ConnectionRequest 将数据发布到格式化文本 字符串 我想知道是否有
  • Spring Batch如何作为Reader读取多个表(查询)并将其写入平面文件写入

    在我的项目中 我读取了具有不同查询的多个表 并将这些结果集合并到平面文件中 我该如何实现这一目标 我的意思是 JdbcReader 直接采用 1 个选择查询 我如何自定义它 如果 JdbcCursorItemReader 不能满足您的需求
  • 定制法国号码格式

    我尝试为美国国家 地区使用自定义数字格式 到目前为止效果很好 Not something I want NumberFormat numberFormat0 NumberFormat getNumberInstance Locale US
  • Android 上的 setTimeOut() 相当于什么?

    我需要等效的代码setTimeOut call function milliseconds 对于安卓 setTimeOut call function milliseconds 您可能想查看定时任务 http developer andro
  • mvn dependency:analyze 结果不正确

    我一直在寻找一种工具 它能够向您显示未使用的依赖项 我很快就偶然发现了 Maven 命令mvn dependency analyze 这样做的问题是 它经常检测到 未使用的 依赖项 如果缺失 这些依赖项就会导致构建失败 这是优化项目的示例
  • JLabel.setText() 中的换行符

    使用 JLabel setText 时如何插入换行符 我尝试使用 Html 但似乎可以使其适用于 setText 仅适用于 jLabel 的初始声明 最初声明 jlabel 时的方法是 label new JLabel Hello Worl
  • 将 Spring Boot 应用程序部署到 Heroku 失败并显示“无效标志:--release -> [帮助 1]”

    当我尝试将代码部署到 Heroku 时 通过git push heroku master 我收到 Maven 错误 remote ERROR Failed to execute goal org apache maven plugins m
  • C# 中的 Culture 相当于 Java 中的 Locale 吗?

    C 使用文化的概念 这在操作上与 Java 中的 Locale 类似吗 或者底层概念是否存在显着差异 从文化而不是语言环境的角度进行工作是一种寻找正确抽象层次的尝试 从以类似方式做事的人群的角度来考虑事物 而不是谈论地理区域和语言 并有点疯
  • 带有 spring-kafka 的 Kafka 死信队列 (DLQ)

    最好的实施方式是什么死信队列 DLQ Spring Boot 2 0 应用程序中的概念 使用 spring kafka 2 1 x 来处理无法处理的所有消息 KafkaListener某些bean发送到某些预定义的Kafka DLQ主题的方
  • Spring Batch:比较数据库之间的数据

    我有两个数据库 Oracle 和 MySQL 目标是将Oracle表中的值保存到MySQL中 要求 MySQL表中不存在数据 但我在理解 Spring Batch 时遇到了困难 步骤中 它包含itemReader itemProcessor
  • 在 Java 和 PHP 之间加密/解密字符串

    我使用 AES 加密来加密和解密服务器端的 php 和 Android 应用程序 作为客户端 之间的字符串 PHP 中的加密字符串为 HaxRKnMxT24kCJWUXaVvqDHahzurJQK sYA4lIHql U 在 Java 中是
  • 从 Java 调用 Python 代码时出现问题(不使用 jython)

    我发现这是从 java 运行 使用 exec 方法 python 脚本的方法之一 我在 python 文件中有一个简单的打印语句 但是 我的程序在运行时什么也没做 它既不打印Python文件中编写的语句 也不抛出异常 程序什么都不做就终止了
  • 如何在最短的时间内克隆java中的输入流

    有人可以告诉我如何克隆输入流 并花费尽可能少的创建时间吗 我需要多次克隆输入流以使用多种方法来处理 IS 我尝试了三种方法 但由于这样或那样的原因 事情不起作用 方法 1 感谢 stackoverflow 社区 我发现以下链接很有帮助 并将
  • 如何在 Eclipse 中使用 Hibernate Tools 生成 DAO?

    我在用着 Eclipse Java EE IDE Web 开发人员 版本 Indigo 发布 使用 hibernate 工具 我对 Eclipse 中的 hibernate 很陌生 所以我学习如何配置 hibernate 并使用注释生成 P
  • JFreeChart MeterPlot

    我目前正在用java做Agent项目 在某些时候 我需要显示一个仪表 例如 电池电量 我的程序中有 5 个代理 每个代理都会创建自己的带有名称的仪表图 但不知何故他们没有更新数据集 或者他们正在更新数据集 只是它没有显示在仪表图上 任何想法
  • 在调试模式下,哪些代码更改会自动反映在 Eclipse 中?

    我使用 eclipse 用于编写 调试 作为 IDE 在调试模式下 当我进行一些更改 例如初始化局部变量 时 它们会自动反映 但其他更改例如更改静态变量的值 有时我会收到一条消息 说我需要重新启动虚拟机 有时则不需要 现在的问题是哪些类型的
  • 如何使用自定义转换器访问 jOOQ 生成的例程字段作为值?

    我在访问生成例程的字段时遇到问题PL pgSQL 用户定义函数 返回JSON 数据类型结果 已经提到this https stackoverflow com q 62535195 6805866问题 这是我的结果get all orders
  • Spring Data JPA 和 Exists 查询

    我正在使用 Spring Data JPA 使用 Hibernate 作为我的 JPA 提供程序 并想要定义一个exists附加 HQL 查询的方法 public interface MyEntityRepository extends C
  • 文件构造函数说明

    我无法理解以下文件构造函数 public File String parent String child and public File File parent String child 参数有什么作用parent and child该文件

随机推荐

  • 华为OD机试真题 Java 实现【字符串通配符】【2022Q4 200分】

    目录 一 题目描述 二 输入描述 三 输出描述 四 解题思路 五 Java算法源码 六 效果展示 一 题目描述 问题描述 在计算机中 通配符一种特殊语法 广泛应用于文件搜索 数据库 正则表达式等领域 现要求各位实现字符串通配符的算法 要求
  • 链式二叉树

    链式二叉树 一 相关函数接口实现 1 前序遍历 2 中序遍历 3 后序遍历 4 节点个数 5 叶子结点个数 6 树的高度 7 第K层结点个数 8 查找值为X的结点 9 通过前序遍历数组构建二叉树 10 销毁二叉树 11 层序遍历 12 判断
  • 数据库领域2023上半年盘点

    国产数据库成发展主力 技术创新成效不俗 2023年上半年 数据库行业整体呈现蓬勃发展态势 特别是国产厂商成为了发展主力军 包括厂商 产品 技术等多方面 中国已经成为全球仅次于美国的数据库发展高地 出现了一大批有代表性的产品及技术创新 据第三
  • 阅读笔记《Learning Attentive Pairwise Interaction for Fine-Grained Classification》

    本文是关于 Learning Attentive Pairwise Interaction for Fine Grained Classification 的阅读笔记 阅读前三个问题 注意力成对交互网络 API Net 互向量学习 Mutu
  • 基于51单片机机械臂控制系统

    1控制系统所运用到的模块 所用到的模块有 pca9685控制多路舵机模块 矩阵按键模块 LCD1702显示模块 DS18B20温度检测模块 独立按键模块 步进电机 ULN2003步进电机控制模 DS1302时钟模块 2控制系统工作模式的功能
  • 腾讯2014校园招聘笔试题

    腾讯 2014校招研发笔试题 试卷类型 软件开发A1 考试时长 120分钟 一 不定向选择题 共25题 每题4分 共100分 多选 错选 少选不得分 1 已知一棵二叉树 如果先序遍历的节点顺序是 ADCEFGHB 中序遍历是 CDFEGHA
  • eclipse启动不了,在加载到最后时刻卡死解决办法

    出现这种情况 关机 重启都无法解决的时候 方法一 到
  • CentOS7使用手册

    2 操作 yum remove openssl 问题 无法进入图形界面 解决 shell登录 执行 yum groupinstall KDE Plasma Workspaces reboot 1 操作 重装系统 然后安装了其它第三方库 问题
  • CentOS8服务篇7:配置网络存储iSCSI服务

    一 iSCSI技术概述 基于IP的存储区域网 Internet Small Computer System Interface iSCSI 又称IP SAN 它是一种基于因特网及SCSI 3协议的存储技术 由IETF提出 并于2003年2月
  • 原来类加载器的原理是这样执行的

    原文链接 深入类加载原理 我们知道 我们写的java文件是不能直接运行的 我们可以在IDEA中右键文件名点击运行 这中间其实掺杂了一系列的复杂处理过程 这篇文章 我们只讨论我们的代码在运行之前的一个环节 叫做类的加载 按照我写文章的常规惯例
  • MyBatis下DataSource的配置原理以及多数据源的配置

    MyBatis下多数据源的配置 多datasource的配置原理 将不同数据源的mapper接口放在不同的包内 再在数据源配置中 用 MapperScan注解扫描不同的包 引言 先来学习一下单数据源情况下的配置原理 只关心多数据源的配置方法
  • Linux常用命令总结

    很全啊 记录一下 https mp weixin qq com s biz MzI3MjY1ODI2Ng mid 2247485996 idx 1 sn 0a08bf2f04eed49542a96cee6928a561 chksm eb2e
  • 第一个 Spring Boot 子服务——会员服务

    经过上两个章节的分析 设计工作 相信你已经对项目的整体结构有了更清晰的认识 剩下的工作就是依据设计 将项目骨架拉出来 往里面直充血肉 搭建项目骨架 约定项目名称为 parking project 建立 Maven 项目 packaging
  • 广州面试题------39健康网1广州启生信息技术有限公司

    部分笔试答案 3 接口 interface 是抽象方法和常量值的定义的集合 从本质上讲 接口是一种特殊的抽象类 这种抽象类中包含常量和方法的定义 而没有变量和方法的实现 1 接口是一组规则的集合 它规定了实现本接口的类或接口必须拥有的一组规
  • 【语义分割】轻量级人像分割PP-HumanSeg NCNN C++ windows部署

    ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架 ncnn 从设计之初深刻考虑手机端的部署和使用 无第三方依赖 跨平台 手机端 cpu 的速度快于目前所有已知的开源框架 基于 ncnn 开发者能够将深度学习算法轻松移植到手机端高
  • C++ std::string 字符串替换

    std string里面std replace只有单字符替换 std replace str begin str end 单字符替换 将双引号换成 符 如果有字符串替换的话需要配合find 使用
  • 【React】根据条件渲染不同的组件内容

    需求如下 但我点击不同的按钮时 下面渲染的是不同组件的内容 wokao这个图片大小咋调 每回都贼大 第一步 先写好不同组件的内容 第二步 在state状态里面加一个Type进行记录 第三步 核心代码如下 好了 说白了就是通过改变type的值
  • php 在线测试 tool,在线程序员工具箱

    Tool lu 将在线工具分为开发类 站长类 极客 Geek 类以及其他 其中收录很多在开发或网站站长会用到的工具 例如 CSS JavaScript PHP 代码优化 压缩 SQL Ruby 和 Json Python 等代码美化 图片编
  • 深度学习——全连接层(Fully connected dence layers)原理解析

    深度学习 全连接层 Fully connected dence layers 原理解析 一 简介 全连接层有多个神经元 是一个列向量 单个样本 在计算机视觉领域正常用于深度神经网络的后面几层 用于图像分类任务 全连接层算法包括两部分 前向传
  • Java集合篇:ArrayList详解

    一 ArrayList概述 ArrayList是实现了List接口的动态数组 所谓动态数组就是他的大小是可变的 实现了所有可选列表操作 并允许包括Null在内的所有元素 除了实现 List 接口外 此类还提供一些方法来操作内部用来存储列表的