【java基础】ArrayList源码解析

news2024/11/25 23:26:58

文章目录

  • 基本介绍
  • 构造器
    • 指定初始容量
    • 默认创建
    • 通过集合创建
  • 添加
    • add扩容机制
    • 批量添加addAll
    • 添加指定位置add
    • 添加多个元素到指定位置addAll
  • 删除
    • 删除指定元素remove
    • 删除指定索引元素remove
    • 条件删除removeIf
    • 批量删除removeAll
  • 修改
    • 修改指定位置set
    • 替换所有满足要求元素replaceAll
  • 一些实用方法
  • 总结

基本介绍

ArrayList是使用数组存储元素的的集合,能够自动进行扩容。ArrayList的类图如下

在这里插入图片描述

该类拥有许多操作集合的方法,在这篇文章中将会debug几个常见的方法。

这里先将ArrayList的成员属性以及注释列出来

    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

构造器

ArrayList的几个构造器如下

public ArrayList(int initialCapacity){
	...
}

public ArrayList() {
	...
}

public ArrayList(Collection<? extends E> c){
	...
}

指定初始容量

在创建ArrayList的时候指定集合的大小

ArrayList<Integer> list = new ArrayList<>(20);

下面来debug一下这个流程。
首先进行到构造方法,
在这里插入图片描述

可以发现这个方法就是简单的判断了一下传入的容量是否大于0,如果大于0,那么初始化elementData,如果等于0,那么就将一个空数组赋值给elementData,否则就抛出异常


默认创建

现在debug一下无参构造器

ArrayList<Integer> list = new ArrayList<>();

在这里插入图片描述

可以看见这个构造器就是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA的引用传递给elementData,下面来看一下elementData

在这里插入图片描述

可以发现就是一个空数组,这个数组会在添加元素的时候初始化大小。构造器结束后发现并没有实际创建数组

在这里插入图片描述

通过集合创建

开始debug参数为Collection的构造器

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));

Arrays.asList会返回一个ArraysList,ArrayList就是Collection的子类,下面为Arrays。asList的源代码

在这里插入图片描述

下面开始debug

在这里插入图片描述

我们来分析一下这个代码

		if ((size = a.length) != 0)

上面是将a数组的长度赋值给size,并且判断是否为0

		    if (c.getClass() == ArrayList.class) {
		        elementData = a;
		    } else {
		        elementData = Arrays.copyOf(a, size, Object[].class);
		    }

上面代码就判断传入的集合是否为ArrayList,如果是,那么就直接赋值,否则就创建一个和a一样大的数组.

            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;

最后这个就是用来出来传入集合为空的。

添加

在这里插入图片描述

可以发现主要有4个方法。add表示添加一个元素,addAll表示批量添加。默认是添加在数组最后一个元素后面的,也可以指定要添加的索引。对于添加到指定索引,这个应该叫修改。

add扩容机制

我们在上面的构造器中看见了如果没有指定初始容量,那么默认的初始大小就是0。我们来看看ArrayList的扩容机制是怎样的

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 11; i++) {
            list.add(i);
        }
    }

上面代码会想集合中添加11个元素,先来看添加第一个元素的流程。首先进入add方法,最开始element为空,所以size也为0

在这里插入图片描述

然后进入ensureCapacityInternal方法

在这里插入图片描述

然后进入calculateCapacity方法,这个方法用来计算最小容量的

在这里插入图片描述

由于我们是通过无参构造器创建的集合,所以elementData就会等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,然后返回一个最小容量Math.max(DEFAULT_CAPACITY, minCapacity),DEFAULT_CAPACITY就是10,minCapacity等于1,所以就会返回10。然后进入ensureExplicitCapacity方法

在这里插入图片描述

这个方法就是用来判断集合所需的最小容量是否大于当前集合的容量,在这里,显然是大于的。然后进入grow方法

在这里插入图片描述

这个方法简单来说就是将当前容量扩大为1.5倍,就是下面的语句

        int newCapacity = oldCapacity + (oldCapacity >> 1);

然后判断扩大后的容量是否满足最小的容量需求

        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

最后就是创建新数组,容量更大(一般情况都是1.5倍大小)。然后elementData 执行新数组

        elementData = Arrays.copyOf(elementData, newCapacity);

然后一直返回到add方法

在这里插入图片描述

此时elementData为容量10的空数组

在这里插入图片描述

然后进行数组赋值

在这里插入图片描述

这样add方法就结束了。现在,我们就可以做一个总结,add方法就是会将元素存储在最后一个元素的后面,但是存储之前会判断一下数组,也就是elementData是否还有足够大的容量。

所以,我们后面存储1-9都不会进行扩容

在这里插入图片描述

当我们存储10的时候,又会将容量扩大为1.5倍(对于添加单个元素),也就是15

在这里插入图片描述


批量添加addAll

批量添加就是addAll,传入应该Collection,然后就会将传入的Collection添加到当前集合

在这里插入图片描述

可以发现这个方法非常简单,就是先判断是否还有足够的容量,然后数组复制。对于

ensureCapacityInternal方法,上面说了对于单个元素基本就是扩容1.5倍了,因为扩容1.5倍后添加一个元素肯定没有问题了,但是对于批量添加的,扩容1.5倍不一定够,对于批量添加可能就是扩容为当前size+传入集合大小。

看下面例子

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 11; i++) {
            list.add(i);
        }
        List<Integer> addList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            addList.add(i);
        }
        list.addAll(addList);
    }

我们直接添加了100个元素,猜一下现在的list大小为多少?现在的大小为111

在这里插入图片描述

原因就是下面的语句

在这里插入图片描述

添加指定位置add

使用add(int index, E element),将指定元素插入到此列表中的指定位置。将当前位于该位置的元素(如果有的话)和所有后续元素向右移动(向它们的下标添加1)。

在这里插入图片描述

我们来分析一下这个方法,首先就是检查索引是否越界

在这里插入图片描述

然后就是移动数组元素,保存指定值

在这里插入图片描述

添加多个元素到指定位置addAll

使用 addAll(int index, Collection<? extends E> c),传入索引和集合

在这里插入图片描述

这个方法和上面基本一样都是数组复制,不细说了


删除

列表删除主要有以下几个方法,下面分别介绍

在这里插入图片描述

删除指定元素remove

remove(Object o),这个方法传入一个元素,从此列表中删除该元素的第一次出现。

在这里插入图片描述

可以发现这个方法十分简单,就是对所有元素进行比较,如果相等就调用fastRemove这个方法,将要删除元素的索引传过去

在这里插入图片描述

上面就是fastRemove,可以发现也是通过数组复制完成的。


删除指定索引元素remove

remove(int index),传入要删除元素的索引

在这里插入图片描述

上面就是该方法的源码,可以发现处理也很简单,就是先检查传入的index是否合法,然后保存要删除的值,然后数组移动,最后返回删除的值。


条件删除removeIf

removeIf(Predicate<? super E> filter) 这个方法就会根据传入的条件来进行删除,下面是该方法的源代码

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

对于上面的方法,其实思路很简单,第一步就是遍历所有元素,然后将要删除的元素索引放入一个Set。(BitSet后续文章会进行说明)

        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }

然后再次遍历集合,用不需要删除的元素来填充删除元素

            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }

最后再将集合中最后x(x表示删除元素的个数)个元素置为null

            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;

批量删除removeAll

removeAll(Collection<?> c),这个方法会删除包含在传入集合中的所有元素。

在这里插入图片描述

该方法会去调用batchRemove方法,下面就直接来看看这个方法

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

这个方法我们来分解一下,首先是

            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];

可以发现,这个循环就理解为将数组不被删除的元素左移即可,被删除的元素将被覆盖

finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }

上面代码的第一个判断没有用,直接忽略,下面一个判断 w != size,就是为了将集合中最后x(x表示删除元素的个数)个元素置为null。

修改

修改元素,我们可以使用set和replaceAll方法,下面来进行说明

修改指定位置set

使用set(int index, E element),该方法内容如下

在这里插入图片描述

可以发现就是检查索引是否越界,然后就将指定索引设置为新值,最后返回旧值

替换所有满足要求元素replaceAll

replaceAll(UnaryOperator operator),这个方法传入一个lambda表达式即可,会对所有满足条件的元素进行更新

在这里插入图片描述

该方法很简单,就是一个for循环,核心语句就是如下语句

            elementData[i] = operator.apply((E) elementData[i]);

如果看不懂,那就是lambda学的不扎实,建议参考 一篇文章彻底搞懂lambda表达式

一些实用方法

上面以及介绍完了ArrayList的很多基本操作,现在给出一些ArrayList的一些实用方法及其说明

  • clear方法,该方法会将集合所有元素清空
    在这里插入图片描述
  • indexOf(Object o) 返回元素第一次出现的索引在这里插入图片描述
  • contains(Object o) 判断集合中是否包含指定元素在这里插入图片描述
  • toArray(T[] a) 将集合转换为数组在这里插入图片描述
  • trimToSize() ,调整集合容量为当前元素个数在这里插入图片描述

总结

对于ArrayList,就理解为一个简单的数组即可,该数组会自动扩容(一般为1.5)。

在ArrayList中,其实还有一些方法没有进行说明,例如forEach,sort,clone,retainAll等等,对于这些方法,原理都比较简单,大家经过上面的学习,自行查看源码即可

ArrayList删除读和取,不擅长修改和删除,如果要频繁使用修改和删除请使用LinkedList。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/403240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vscode环境配置(支持跳转,阅读linux kernel)

目录 1.卸载clangd插件 2.安装C插件 3. 搜索框内输入 “intell”&#xff0c;将 C_Cpp&#xff1a;Intelli Sense Engine 开关设置为 Default。 4.ubuntu安装global工具 5.vscode安装插件 6.验证是否生效 7.建立索引 1.卸载clangd插件 在插件管理中卸载clangd插件 2.安…

课设-机器学习课设-实现新闻分类

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;课设-机器学习 &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c;…

linux下安装SonarQube

目录1. 准备安装环境2. 安装postgres数据库3. 安装SonarQube4. 使用SonarQube1. 准备安装环境 这里安装SonarQube的系统环境是Red Hat Enterprise Linux release 8.7 &#xff0c;然后将jdk的压缩包&#xff08;jdk-17.0.2_linux-x64_bin.tar.gz&#xff09;和sonarQube的压缩…

Web Components学习(2)-语法

一、Web Components 对 Vue 的影响 尤雨溪在创建 Vue 的时候大量参考了 Web Components 的语法&#xff0c;下面写个简单示例。 首先写个 Vue 组件 my-span.vue&#xff1a; <!-- my-span.vue --> <template><span>my-span</span> </template>…

Spring——spring整合JUnit

JUnit定义: Junit测试是程序员测试&#xff0c;即所谓 白盒测试 &#xff0c;因为程序员知道被测试的软件如何&#xff08;How&#xff09;完成功能和完成什么样&#xff08;What&#xff09;的功能。 Junit是一套框架&#xff0c;继承TestCase类&#xff0c;就可以用Junit进行…

基于Selenium+Python的web自动化测试框架(附框架源码+项目实战)

目录 一、什么是Selenium&#xff1f; 二、自动化测试框架 三、自动化框架的设计和实现 四、需要改进的模块 五、总结 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 一、什么是Selenium&#xff1f; …

SpringBoot bean 加载顺序如何查看(源码解读)

背景 SpringBoot bean 加载顺序如何查看&#xff0c;想看加载了哪些bean&#xff0c; 这些bean的加载顺序是什么&#xff1f; 实际加载顺序不受控制&#xff0c;但会有一些大的原则&#xff1a; 1、按照字母顺序加载&#xff08;同一文件夹下按照字母数序&#xff1b;不同文件…

界面开发(4)--- PyQt5实现打开图像及视频播放功能

PyQt5创建打开图像及播放视频页面 上篇文章主要介绍了如何实现登录界面的账号密码注册及登录功能&#xff0c;还简单介绍了有关数据库的连接方法。这篇文章我们介绍一下如何在设计的页面中打开本地的图像&#xff0c;以及实现视频播放功能。 实现打开图像功能 为了便于记录实…

CobaltStrike攻击payload(有效载荷)介绍

HTA文档Office宏payload生成器有效载荷生成器windows可执行程序windows可执行程序windows stageless生成所有有效载荷HTA文档该模块为HTML Application attack&#xff08;HTML应用攻击&#xff09;。简单来说&#xff0c;就是这个包生成一个运行有效负载的HTML应用程序该模块下…

TCP UPD详解

文章目录TCP UDP协议1. 概述2. 端口号 复用 分用3. TCP3.1 TCP首部格式3.2 建立连接-三次握手3.3 释放连接-四次挥手3.4 TCP流量控制3.5 TCP拥塞控制3.6 TCP可靠传输的实现3.7 TCP超时重传4. UDP5.TCP与UDP的区别TCP UDP协议 1. 概述 TCP、UDP协议是TCP/IP体系结构传输层中的…

Flink 定时加载数据源

一、简介 flink 自定义实时数据源使用流处理比较简单&#xff0c;比如 Kafka、MQ 等&#xff0c;如果使用 MySQL、redis 批处理也比较简单 如果需要定时加载数据作为 flink 数据源使用流处理&#xff0c;比如定时从 mysql 或者 redis 获取一批数据&#xff0c;传入 flink 做处…

三、HTTP协议之三

文章目录一、HTTPS协议概述二、 HTTPS使用成本三、从HTTP到HTTPS四、HTTP协议的瓶颈五、双工通信的websocket六、HTTP2.0一、HTTPS协议概述 二、 HTTPS使用成本 HTTPS对性能的影响 https之所有安全是因为使用TLS(SSL)来加密传输. 三、从HTTP到HTTPS 了解个大概 第一步&#x…

react:二、jsx语法规则

目录 1.传输数据的xml和json格式举例 2.jsx语法规则 3.js语句跟js表达式的区别 4.jsx的小练习 1.传输数据的xml和json格式举例 2.jsx语法规则 1.定义虚拟DOM时&#xff0c;不要写引号。 2.标签中混入JS表达式时要用{}。 3.样式的类名指定不要用class&#xff0c;要用cla…

第十一章 寡头垄断市场中的企业决策

寡头垄断市场的定义、条件 寡头垄断市场&#xff1a;少数几家企业控制了某一行业的市场&#xff0c;供给该行业生产的大部分产品 寡头垄断市场应具备的条件&#xff1a; 一个行业或市场中&#xff0c;只有少数几家企业企业之间存在着相互制约、相互依存的关系新企业进入行业比…

golang大杀器GMP模型

golang 大杀器——GMP模型 文章目录golang 大杀器——GMP模型1. 发展过程2. GMP模型设计思想2.1 GMP模型2.2 调度器的设计策略2.2.1 复用线程2.2.2 利用并行2.2.3 抢占策略2.2.4 全局G队列2.3 go func()经历了那些过程2.4 调度器的生命周期2.5 可视化的CMP编程2.5.1 trace方式2…

【LeetCode】33. 搜索旋转排序数组、1290. 二进制链表转整数

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 33. 搜索旋转排序数组 1290. 二进制链表转整数 33. 搜索旋转排序数组 33. 搜索旋转排序…

JavaEE简单示例——Bean的实例化

简单介绍&#xff1a; 在我们之前使用某个对象&#xff0c;那么就要创建这个类的对象&#xff0c;创建对象的过程就叫做实例化。对于Spring来说&#xff0c;实例化Bean的方式有三种&#xff0c;分别是构造方法实例化&#xff0c;静态方法实例化&#xff0c;实例工厂实例化。我…

哪款手推式洗地机好用?2023洗地机推荐

虽然现在市面上的洗地机层出不穷&#xff0c;但是无论洗地机怎么变&#xff0c;关于洗地机的选择看准吸力、除菌、续航、清洁力这几点就够了。因此&#xff0c;一款好用的洗地机必须要拥有良好的清洁力和续航时间&#xff0c;最好还拥有除菌等细节功能。那么下面就让我们一起来…

【Linux】文件系统详解

&#x1f60a;&#x1f60a;作者简介&#x1f60a;&#x1f60a; &#xff1a; 大家好&#xff0c;我是南瓜籽&#xff0c;一个在校大二学生&#xff0c;我将会持续分享C/C相关知识。 &#x1f389;&#x1f389;个人主页&#x1f389;&#x1f389; &#xff1a; 南瓜籽的主页…

Unity脚本复习

1.在Project面板中显示和创建的每一个脚本其实都是一个类&#xff0c;当我们把脚本挂载到Hierarchy层级中的游戏物体时&#xff0c;其实我们就实现了将脚本类实例化为一个脚本组件&#xff08;对象&#xff09;的过程 2.在游戏运行时&#xff0c;场景加载&#xff0c;游戏对象…