ArrayList 源码解析和设计思路

ArrayList

  • 一、继承体系
  • 二、接口继承
  • 三、标记接口
  • 四、设计目的
  • 五、框架总体结构
  • 六、工作原理
  • 七、创建List对象初始化?还是add()添加元素初始化?
  • 七、add(E e)添加元素
  • 八、remove(int index)删除元素
  • 八、线程安全问题

一、继承体系

  • ArrayList 继承自 AbstractList,AbstractList 实现了 List 接口的部分方法,为 List 的具体实现类提供了方便。
  • AbstractList 是一个抽象类,它实现了 Collection 接口,也就间接实现了 Iterable 接口。

二、接口继承

  • List 接口继承自 Collection 接口,Collection 继承自 Iterable 接口。
  • List 接口代表有序的队列

三、标记接口

  • RandomAccess 是一个标记接口,标识 ArrayList 支持快速随机访问,可以通过索引直接访问元素。
  • CloneableSerializable 也是标记接口,前者表示可复制克隆,后者表示可序列化。

四、设计目的

  • Java 集合框架的设计目的是为了更好地对集合数据进行操作,统一了访问方式,增强了代码的可读性、简洁性和可扩展性。
  • 使用接口和抽象类来定义规范,然后由具体实现类来实现不同的数据结构和算法。
  • 标记接口则用来对类型进行标记和区分,以便采取不同的算法和优化。

五、框架总体结构

  • 最顶层是 Collection接口,定义了集合的基本操作。
  • 然后分为 ListSetQueue 等子接口,分别定义了有序、无序、队列等特性。
  • 再通过 AbstractListAbstractSet 等抽象类为子接口提供部分方法实现。
  • 最后是具体的实现类,如 ArrayListLinkedListHashMap等。

六、工作原理

ArrayList 底层是通过动态数组(可自动扩容)实现的,它的工作原理如下:

  • 初始化时,为一个初始容量为0的数组
  • 当添加元素时,容量不够则自动扩容(默认扩容为原来的1.5倍)
  • 插入和访问元素通过索引直接操作数组,性能较高
  • 删除元素需要移动其他元素以保证连续存储
  • 由于自动扩容,在末尾插入元素效率很高,但在中间位置插入效率较低

ArrayList 的源码注释如下:

/*** 默认初始容量为 10*/
private static final int DEFAULT_CAPACITY = 10;/*** 用于储存元素的数组缓冲区*/
transient Object[] elementData; /*** ArrayList 中元素的数量*/
private int size;// 其他方法...

可以看出,ArrayList 内部通过一个 Object 数组存储元素,size 变量记录当前元素数量,通过自动扩容机制来支持动态增长。这种基于动态数组的实现,决定了 ArrayList 查询快、增删慢的特点。

七、创建List对象初始化?还是add()添加元素初始化?

在 Java JDK 1.8 中,ArrayList 的默认初始容量 10 是在第一次通过无参构造函数创建 ArrayList 对象时初始化的,而不是在添加第一个元素时初始化。

ArrayList 有三种构造方式:

  1. ArrayList()

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

    使用无参构造函数时,底层会初始化一个初始容量为 10 的空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. ArrayList(int initialCapacity)

    /*** 构造一个具有指定初始容量的空列表*/
    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);}
    }
    

    使用这个指定容量的构造函数,会根据指定的容量初始化底层数组大小。

  3. ArrayList(Collection<? extends E> c)

    /*** 构造一个包含指定collection的元素的列表,按迭代器返回元素的顺序*/
    public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {// c.toArray可能(错误地)不同时使用Object[]...if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// 使用空数组替换以允许缓冲区进行正常增长this.elementData = EMPTY_ELEMENTDATA;}
    }
    

    使用这个构造函数会根据提供的集合的大小创建底层数组。

使用无参构造函数 ArrayList() 时,底层数组的初始容量才会被初始化为 10。其他两种构造函数要么根据用户指定的容量初始化,要么根据提供的集合大小初始化。而添加元素时则不会再初始化容量,只是在容量不足时按照需求扩容。

七、add(E e)添加元素

    public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;}
  1. ensureCapacityInternal(size + 1): 这一行代码调用了ensureCapacityInternal方法,它用于确保ArrayList内部数组的容量足够来存放新增的元素。如果需要增加ArrayList的容量,它会创建一个新的更大的数组,并将原来的元素复制到新数组中。

  2. elementData[size++] = e;: 这一行代码将新的元素e添加到ArrayList的内部数组elementData中,并且将size变量递增,表示列表中的元素数量增加了一个。这里使用size++是因为要先将元素添加到size所指示的位置,然后再递增size,以便下次添加元素时能够添加到正确的位置。

  3. return true;: 最后,方法返回true,表示添加操作成功。


    private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}

这段代码是ArrayList中的ensureCapacityInternal(int minCapacity)方法的部分源码

  1. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {: 这一行代码用于检查elementData是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,该常量表示一个空数组。如果是空数组,说明当前ArrayList没有初始化容量,需要根据默认容量和需要添加的最小容量来确定实际容量。

  2. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);: 如果elementData是空数组,则会根据默认容量DEFAULT_CAPACITY和需要添加的最小容量来确定实际的最小容量。DEFAULT_CAPACITYArrayList默认的初始容量大小。

  3. ensureExplicitCapacity(minCapacity);: 接下来调用ensureExplicitCapacity(minCapacity)方法来确保ArrayList的内部数组容量能够容纳至少minCapacity个元素。这个方法会比较当前数组的容量和需要的最小容量,如果当前容量不足,则会扩大内部数组的容量以满足需求。


   private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}

-这段代码是ArrayList中的ensureExplicitCapacity(int minCapacity)方法的部分源码

  1. modCount++;: 这一行代码递增了modCount变量的值。modCount用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount会递增,表示ArrayList的结构发生了变化。

  2. if (minCapacity - elementData.length > 0) grow(minCapacity);: 这里使用了溢出安全的计算方式来判断是否需要扩容内部数组。如果当前容量不足以容纳最小容量minCapacity个元素,则需要调用grow(minCapacity)方法来扩大内部数组的容量。

  3. grow(int minCapacity): 当需要扩容时,会调用grow(int minCapacity)方法来实现容量的扩充。grow()方法会根据需要的最小容量来确定新的容量大小,并将原来的元素复制到新的更大的数组中。


    private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}

这段代码是ArrayList中的grow(int minCapacity)方法的部分源码

  1. int oldCapacity = elementData.length;: 获取当前内部数组的容量,即原来的容量大小。

  2. int newCapacity = oldCapacity + (oldCapacity >> 1);: 原来的容量大小计算出新的容量大小。新的容量大小会是原来容量的1.5倍,通过右移一位实现了除以2的操作。原来容量大小 + 容量大小的一半

  3. if (newCapacity - minCapacity < 0) newCapacity = minCapacity;: 如果新的容量仍然小于需要的最小容量minCapacity,则将新的容量调整为minCapacity,确保容量能够满足需求。

  4. if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);: 检查新的容量是否超出了最大数组大小MAX_ARRAY_SIZE,如果超出则调用hugeCapacity()方法来确定合适的容量大小,这是为了避免数组大小超出Java虚拟机的限制。

  5. elementData = Arrays.copyOf(elementData, newCapacity);: 最后,使用Arrays.copyOf()方法将原来的元素复制到新的更大的数组中,完成了内部数组的扩容过程。

grow()方法主要负责处理ArrayList内部数组的扩容问题。它会根据旧的容量大小计算出新的容量大小,并根据需要调整为满足最小容量要求的合适大小。然后将原来的元素复制到新的更大的数组中,完成数组的扩容操作。这样可以确保在需要添加大量元素时,ArrayList内部数组能够容纳足够多的元素。

八、remove(int index)删除元素

    public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;}

这段代码是ArrayList中的remove(int index)方法的部分源码

  1. rangeCheck(index): 这一行代码调用了rangeCheck方法来检查索引是否越界,确保要移除的元素在有效范围内。如果索引不在有效范围内,会抛出IndexOutOfBoundsException

  2. modCount++: 这里递增了modCount变量的值。modCount用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount会递增,表示ArrayList的结构发生了变化。

  3. E oldValue = elementData(index): 这一行代码获取要移除的元素,并将其保存为oldValue

  4. int numMoved = size - index - 1;: 计算需要向前移动的元素个数,即原始数组中位于被删除元素后面的元素个数。

  5. System.arraycopy(elementData, index+1, elementData, index, numMoved): 如果存在需要向前移动的元素,则使用System.arraycopy方法将这些元素向前移动,以填补被删除元素的位置。

  6. elementData[--size] = null: 将列表中最后一个元素置空,以便让GC进行清理工作。

  7. 返回oldValue:返回被删除的元素的值。

通过以上步骤,remove()方法实现了在指定位置上删除元素的功能。它首先进行了参数的合法性检查,然后递增了modCount,接着移动需要向前移动的元素,将列表中最后一个元素置空,并返回被删除的元素的值。


    public boolean remove(Object o) {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;}

这段代码是ArrayList中的remove(Object o)方法的部分源码

  1. 如果传入的对象 o 为空(null):

    • 通过 for 循环遍历 elementData 数组,寻找为 null 的元素。
    • 当找到第一个为 null 的元素后,调用 fastRemove(index) 方法进行删除,并返回 true。
  2. 如果传入的对象 o 不为空(non-null):

    • 通过 for 循环遍历 elementData 数组,寻找与传入对象相等的元素。
    • 当找到第一个与传入对象相等的元素后,调用 fastRemove(index) 方法进行删除,并返回 true。
  3. 如果未找到符合条件的元素,则直接返回 false。

fastRemove(index) 方法用于快速删除指定索引位置的元素。通过这种方式,remove(Object o) 方法能够在列表中移除满足特定条件的第一个元素,并返回是否成功移除的布尔值。

八、线程安全问题

ArrayList存在线程安全问题的本质在于其内部的elementData、size和modCount等变量,在进行各种操作时没有加锁,并且这些变量的类型也不是volatile的。因此,如果多个线程对这些变量进行操作,则可能出现值被覆盖的情况。需要强调的是,只有当ArrayList作为共享变量时,才会出现线程安全问题;当ArrayList是方法内的局部变量时,则不存在线程安全问题。

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

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

相关文章

智慧城市革命,物联网技术如何改变城市治理与生活方式

随着科技的不断进步&#xff0c;智慧城市已经成为现代城市发展的重要方向之一。物联网技术作为智慧城市的重要支撑&#xff0c;正深刻改变着城市的治理模式和居民的生活方式。本文将探讨智慧城市革命&#xff0c;以及物联网技术如何改变城市治理与生活方式&#xff0c;同时介绍…

Python Web开发记录 Day12:Django part6 用户登录

名人说&#xff1a;东边日出西边雨&#xff0c;道是无晴却有晴。——刘禹锡《竹枝词》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、登录界面2、用户名密码校验3、cookie与session配置①cookie与session②配置…

STM32CubeMX与HAL库开发教程八(串口应用/轮询/中断/DMA/不定长数据收发)

目录 前言 初识串口-轮询模式 串口中断模式收发 串口DMA模式 蓝牙模块与数据包解析 前言 前面我们简单介绍过串口的原理和初步的使用方式&#xff0c;例如怎么配置和简单的收发&#xff0c;同时我们对串口有了一个初步的了解&#xff0c;这里我们来深入的来使用一下串口 …

Linux服务器(RedHat、CentOS系)安全相关巡检shell脚本

提示&#xff1a;巡检脚本可以使用crontab定时执行&#xff0c;人工根据执行结束时间点统一收集报告文件即可。 #!/bin/bash# Define output file current_date$(date "%Y%m%d") # Gets the current date in YYYYMMDD format echo >server_security_inspection_r…

Leetcode 第 126 场双周赛 Problem C 替换字符串中的问号使分数最小(Java + 排序 + 贪心)

文章目录 题目思路Java 排序 贪心第一步&#xff1a;第二步&#xff1a;第三步&#xff1a; 复杂度Code 题目 Problem: 100249. 替换字符串中的问号使分数最小给你一个字符串 s 。s[i] 要么是小写英文字母&#xff0c;要么是问号 ‘?’ 。对于长度为 m 且 只 含有小写英文字…

任职资格中的一些面试题和知识点

目录 一、计算机数据结构基本概念理解&#xff1a;1. 数组基本概念优缺点以及如何改进常用的操作底层实现还有哪些容易问到的问题&#xff1a;关于数组的一些衍生数据结构和算法问题 2. 链表基本概念&#xff1a;链表的节点是什么&#xff1f;每个节点包含哪些信息&#xff1f;…

【计算机网络】https的工作原理以及和http的区别

目录 前言 1. HTTP协议存在的问题 2. 什么是HTTPS协议&#xff1f; 3. HTTP和HTTPS有哪些区别&#xff1f; 4. HTTPS的工作原理 加密方式 前言 在日常的Web项目练习中&#xff0c;我们会发现老师会让我们在打开服务器之后使用 http://localhost/...进行项目效果测试和预览…

2.26OS分类,中断(内,外),系统调用,操作系统结构、引导,虚拟机(两类VMM),进程

外核可以申请分配连续的磁盘块以支持频繁的随机访问&#xff0c;其它的方式是采用虚拟存储 分层结构

MongoDB yum安装教程

1 配置yum源 cat /etc/yum.repos.d/mongodb-org-6.0.repo ##################################################### [mongodb-org-6.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/6.0/x86_64/ gpgcheck1 enabled1 gpgkeyht…

iPhone 的健康数据采用的是 FHIR 传输格式

虽然感觉 FHIR 的数据传输格式还是有点繁琐的&#xff0c;但貌似现在也是唯一的事实上的标准。 通过 iPhone 健康上面查看的数据来看&#xff0c;有关健康的数据还是使用 FHIR 的数据传输格式。 不管怎么样&#xff0c;针对老旧的数据传输格式来看&#xff0c;FHIR 至少目前还是…

实现HBase表和RDB表的转化(附Java源码资源)

实现HBase表和RDB表的转化 一、引入 转化为HBase表的三大来源&#xff1a;RDB Table、Client API、Files 如何构造通用性的代码模板实现向HBase表的转换&#xff0c;是一个值得考虑的问题。这篇文章着重讲解RDB表向HBase表的转换。 首先&#xff0c;我们需要分别构造rdb和hba…

学习开发小程序的起航日记

2024年3月16日 不知不觉中三月份还只剩了一半的光景&#xff0c;我想写的内容还很多没有写&#xff0c;或者更应该说&#xff0c;是想积累的还有很多。现在最应该去完善Java的内容&#xff0c;可还是想先等等。想等搞清楚小程序部分&#xff0c;想等积累完小程序的内容。 这几…

PBOOTCMS在nginx虚拟主机location配置中添加规则

PBOOTCMS在nginx虚拟主机location配置中添加规则&#xff0c;规则如下&#xff1a; 1、X版本使用如下规则&#xff1a; location / { if (!-e $request_filename){ rewrite ^/(.*)$ /index.php/$1 last; } } 2、X版本使用如下规则&#xff1a; location / { if (!-e $reques…

Golang 泛型定义类型的时候前面 ~ 代表什么意思

先看代码&#xff0c;定义一个简单的泛型 c1 里面一个 int &#xff0c;定义一个函数goods 下面 main函数进行调用, 如果直接传int 类型是不会报错的,但是如果传自定义类型的b就会报错。 type c1 interface {int }func goods[T c1](a T) {fmt.Println(a) }type myint intfunc …

网络安全——关于防火墙

网络安全防火墙是很重要的部分&#xff0c;关于防火墙我们要知道&#xff0c;他默认所有流量都是黑名单&#xff0c;只有开启允许通过才可以。 我们通过一个实验来学防火墙命令。 防火墙要登录才能使用&#xff0c;用户名是admin,默认密码是Admin123&#xff0c;在第一次登录…

Visual Studio配置libtorch(cuda安装一步到位)

Visual Studio配置libtorch visual Studio安装cuDNN安装CUDAToolkit安装libtorch下载Visual Studio配置libtorch(cuda版本配置) visual Studio安装 visual Studio点击安装 具体的安装和配置过程这里就不进行细讲了&#xff0c;可以参考我这篇博客Visual Studio配置OpenCV(保姆…

Java关于物联网消息引擎:EMQ X

1.背景 1、5G 时代&#xff0c;万物互联 随着5G的到来&#xff0c;万物互联已经成为现实&#xff0c;物联网行业得以蓬勃发展&#xff0c;催生了很多的应用&#xff0c;比如&#xff1a;物联网pass平台&#xff0c;车联网&#xff0c;面向云平台的IOT-Hub&#xff0c;NB-IoT蜂…

Simulink|局部遮荫下光伏组件多峰值PSO-MPPT控制

目录 主要内容 1.光伏电池工程数学模型的输出特性程序 2.普通扰动观察法进行MPPT 3.基于粒子群寻优的多峰输出特性 4.PSO_MPPT仿真模型 程序下载链接 主要内容 在实际的光伏发电系统中,由于环境多变等因素的影响,当局部出现被遮挡情况时光伏阵列的功率-电压(P-U)特…

Java集合类:List、Set、Map常用集合解析

Java集合类&#xff1a;List、Set、Map常用方法解析 文章目录 Java集合类&#xff1a;List、Set、Map常用方法解析一、List集合&#xff1a;有序的元素集合1. ArrayList2. LinkedList 二、Set集合&#xff1a;无序的不重复元素集合1. HashSet2. TreeSet 三、Map集合&#xff1a…

思科CISCO ASA 5555防火墙如何新增一条Ipsec隧道至深信服防火墙

环境: 总部:深信服 AF 8.0.75 分部:思科AF ASA 5555 Cisco Adaptive Security Appliance Software Version 9.4(2)6 Device Manager Version 7.5(2)153 问题描述: 思科CISCO ASA 5555防火墙如何新增至深信服设备 Ipsec隧道 解决方案: Ipsec知识 IKE模式(第1阶段)…