【List篇】ArrayList 详解(含图示说明)

Java中的ArrayList是一个动态数组,可以自动扩展容量以适应数据的添加和删除。它可以用来存储各种类型的数据,例如String,Integer,Boolean等。ArrayList实现了List接口,可以进行常见的List操作,例如添加、插入、删除和访问元素等。

在这里插入图片描述

ArrayList具有以下特点:

  • 有序的 :元素按照添加顺序排列。
  • 可重复的 :同一元素可以重复出现在不同位置。
  • 可变的 :可以动态调整数组大小。
  • 线程不安全 :在多线程环境下需要进行额外的同步措施。
  • 查询效率快。这是由于底层的数据结构是基于Object 数组实现的,且数组在内存中是一块连续的空间,所以每次执行get方法获取元素时,可以通过索引 + 地址的方式能够快速的在数组上的指定位置获取元素
  • 增删效率低。在执行add方法时,可能存在 扩容 ,就需要生成一个新的数组,然后将旧数组中的元素复制到新数组中,最后将新增的元素放在数组上;执行remove方法时,将指定位置元素删除后,后面所有元素向 左移 动一位;因为增删需要对数组的结构进行调整,所以效率低
  • 支持序列化: 实现 Serializable标记性接口。
  • 支持克隆功能: 实现 Cloneable 标记性接口。
  • 支持随机访问功能: 实现 RandomAccess 标记性接口。也就是通过下标获取元素对象的功能
  • 继承 Iterable 接口,可以使用 for-each 迭代

源码分析(JDK1.8)

成员变量属性

/*** 默认长度为10*/private static final int DEFAULT_CAPACITY = 10;/*** 用于空实例的共享空数组实例* 是为了优化创建ArrayList空实例时产生不必要的空数组,使所有的ArrayList空实例底* 层数据结构都指向同一个空数组* 例如:当构造参数是指定的长度,且为0 *      当构造参数是一个集合,且该参数集合中的元素个数为0*/private static final Object[] EMPTY_ELEMENTDATA = {};/*** 用于默认大小的空实例的共享空数组实例。* 将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要扩容多少* 用于标记当前ArrayList是使用空参构造的,在第一次使用add添加元素时,数组的最大长度直接使用默认的10*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** ★ 存储ArrayList元素的数组缓冲区,ArrayList的容量就是这个数组缓冲区的长度* 添加第一个元素时,任何elementData==DEFAULTCAPACITY_empty_elementData的空ArrayList* 都将扩展为DEFAULT_CAPACITY(默认初始容量10)*/transient Object[] elementData; /*** ★ 记录ArrayList 的元素个数*/private int size;/*** ★ 数组最大的长度*/private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

ArrayList 有三个构造函数,空参构造方法,指定初始容量值构造方法和指定集合元素列表的构造方法,如果集合中的元素不为空,新建的ArrayList集合中的元素顺序就是构造参数中的元素顺序

  • 空参构造
    之前在网上看到一个说法" 使用空参构造一个ArrayList时,默认会创建一个容量为10的数组",这个说法其实不准确。在JDK1.8以前是这样的,但是为了节省内存资源,在JDK1.8版本之后,使用空参构造方法,只会创建一个空底层数据结构长度为0的空数组结构,当第一次执行add添加元素时,才会将数组容量扩充到默认10
 public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
  • 指定初始容量值构造
    如果在构建ArrayList之前,就能明确元素的个数,那么就可以在构造ArrayList时,指定容量大小,这样就可以节省因扩容而造成的资源浪费
 public ArrayList(int initialCapacity) {if (initialCapacity > 0) {//当指定参数大于0时,真实存储数据的数据长度就是指定值this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {//当指定参数等于0时,使用空数组this.elementData = EMPTY_ELEMENTDATA;} else {//指定的长度参数不能小于0throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}
  • 构造参数是一个集合,并按照参数集合中元素个数和顺序返回新的ArrayList
 public ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {//如果构造参数集合的类型是ArrayList,那么构造参数的数组结构直接使用elementData = a;} else {//如果构造参数集合的类型不是ArrayList//先初始化的数组长度 = 构造参数集合元素个数//再将构造参数集合中的数据信息,copy到新的数组中elementData = Arrays.copyOf(a, size, Object[].class);}} else {//如果构造参数elementData = EMPTY_ELEMENTDATA;}}

ArrayList 的增删查

add(), 插入元素方法

ArrayList 的add方法有两个,尾部插入指定位置插入

  • 末尾添加
    顾名思义就是直接在数组中最后一个元素的下一个位置上存放新添的元素,在添加元素之前,会校验数组容量是否已经到达临界值,如果到达临界值了,就先扩容,再将新元素添加到扩容后的新数组结构中的最后一个元素后面
public boolean add(E e) {//确定底层elementData的数组长度,且校验是否需要扩容//size + 1 可以理解为假设长度,后面会和当前数组的实际长度相比较,以判断是否需要扩容ensureCapacityInternal(size + 1);   // ★ 扩容核心代码//添加元素,实际上就是赋值的操作elementData[size++] = e;return true;
}

在这里插入图片描述

  • 指定位置插入
    也叫插队添加,会打乱原本数组中元素的存储顺序。大致步骤如下:
    1.校验数组是否有足够的空间
    2.如果空间足够,直接将index及其之后的所有元素向后移动一位
    3.如果空间不够,那么先进行扩容,扩容后的新数组长度是旧数组长度的1.5倍,然后将index位置之前的元素全部等位copy到新数组中,index之后的元素全部以index + 1 的形式偏移一位copy到新数组中
public void add(int index, E element) {//校验参数index值是否大于数组的长度rangeCheckForAdd(index);//确定底层elementData的数组长度,且校验是否需要扩容//size + 1 可以理解为假设长度,后面会和当前数组的实际长度相比较,以判断是否需要扩容ensureCapacityInternal(size + 1);  //指定位置后面的元素,全部copy一份,并向右移动一位,以便于空出位置System.arraycopy(elementData, index, elementData, index + 1,size - index);//最后对指定位置赋值elementData[index] = element;size++;
}

在这里插入图片描述

扩容机制

上面有描述,ArrayList底层是一个动态数组,何为动态?就是在ArrayList每次在执行add方法时,都会先计算一下,假设当前元素添加成功后,数组的长度是否已经超过实际长度,如果超过,那么就自动进行扩容
简单看一下扩容机制源码:

/*** 计算数组长度*/
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//这里可以看到,当使用空参或者指定长度参数,指定构造参数集合元素为0//计算结果:此处所需的最小数组长度为10return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}private void ensureExplicitCapacity(int minCapacity) {modCount++;  //记录数组长度修改次数// overflow-conscious codeif (minCapacity - elementData.length > 0)//当所需最小数组长度 大于 当前实际长度时,进行扩容grow(minCapacity);  //★ 核心扩容机制
}/*** 扩容机制*/private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length; //扩容前的数组长度//扩容后的新数组长度 = 扩容前的数组长度 * 1.5int 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://按照新的长度创建一个新数组,然后将原数组的数据copy到新数组中,并将add的元素加入到数组中elementData = Arrays.copyOf(elementData, newCapacity);}

remove(), 删除元素方法

ArrayList 的remove方法有两个,指定元素删除指定位置删除

  • 指定元素删除
    由于ArrayList 允许元素重复,所以指定元素删除方法可能存在删除多个。
public boolean remove(Object o) {if (o == null) {//如果指定删除的元素是null,那么循环去校验是由有元素为null//如果存在为null的元素,那么就匹配上,然后在内存中新开辟一个数组空间,将旧数组上,//从匹配上位置左边全部   按原index位置copy到新数组中,右边的全部按照index -1 的位置copy到新数组中//匹配几次,就需要重新修改几次数组的数组结构for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {//如果指定删除的元素不是null,那么循环去校验去匹配//同理for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;
}/** 快速删除方法*/
private void fastRemove(int index) {modCount++;  //记录数组结构修改的次数int numMoved = size - index - 1;  //统计需要移动的元素个数if (numMoved > 0)//参数1:elementData  旧数组//参数2:index+1  源数组起点//参数3:elementData 新数组//参数4:index  新数组起点//参数5:numMoved 复制多少位System.arraycopy(elementData, index+1, elementData, index,numMoved);//数组中的最后一位置为null,地址引用删除,方便GC清除    elementData[--size] = null; 
}

在这里插入图片描述

  • 指定位置删除
    最多只会删除一个元素,如果指定位置index超出数组结构长度,报错 IndexOutOfBoundsException
  public E remove(int index) {//校验指定index是否超出数组结构长度rangeCheck(index);modCount++;  //记录数组结构发生调整的次数//通过指定索引index,获取数组元素信息E oldValue = elementData(index);//统计需要移动的元素个数int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);//数组中的最后一位置为null,地址引用删除,方便GC清除    elementData[--size] = null; // clear to let GC do its workreturn oldValue;}/*** 校验指定index是否超出数组结构长度*/private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

在这里插入图片描述
ArrayList在执行remove()方法做删除之后,数组中可能会出现大量的空闲空间。而ArrayList没有自动缩容机制,导致底层大量的空闲空间不能被释放,造成内存浪费。对于这种场景,ArrayList也提供了相应的处理方法,如下:

/*** 将此ArrayList实例的容量修改为列表的当前大小。*/public void trimToSize() {//记录数组结构发生调整的次数modCount++;//size 是当前数组中的元素个数//当元素个数小于数组实际长度时,做缩容处理; 比例按照元素个数进行缩容if (size < elementData.length) {//实际元素个数为0,直接将数组置为空数组elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);  //生成新数组, 长度 = 元素个数,将元素copy到新数组中}}

ArrayList 的增删方法总结, 为什么ArrayList是增删慢的?
从上文介绍可知, add() 方法会存在扩容场景, remove() 会存在移动元素的场景, 这些都会对性能产生很大的影响。用时间复杂度来表示是O(n),所以增删效率低

get(), 取元素方法

ArrayList 的get方法只有一个,只能通过索引下标index获取

public E get(int index) {//校验指定index是否超出数组结构长度rangeCheck(index);//直接通过索引index获取指定位置的value值return elementData(index);
}/***  校验index的有效性*  index 不能超过数组元素的个数,否则会报数组越界异常*/
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

首先数组是存在堆内存中,一块连续的内存空间,并且ArrayList的底层是Object[]数组, 那么数组中的所有元素所占用的字节大小是一致的,所以想要从堆内存中获取元素信息,就必须知道index位置元素在堆内存中的内存地址。数组上元素的内存地址,主要就是看index下标对应的偏移量,这里就需要计算:
公式: 数组上元素的内存地址 = 数组的起始地址 + index * 元素的大小(单位是字节,)
计算出index对应的元素内存地址后, 即可获取到对应数组上元素信息, 而数组中存放的是元素实际数据信息的内存地址,再通过此地址获取到实际元素数据信息
在这里插入图片描述
由于get()取元素方法,是通过下标index 精准定位,继而获取元素信息的, 这期间没有所谓的扩容,copy,迁移等等场景, 用时间复杂度来表示是O(1),所以效率高。

set(), 修改元素方法

ArrayList 中的修改方法只有一个,通过index下标来精准修改, 修改元素信息的步骤,其实就是同一个位置的信息覆盖操作

 public E set(int index, E element) {//校验指定index是否超出数组结构长度rangeCheck(index);//通过index,获取旧的元素信息E oldValue = elementData(index);//然后覆盖替换elementData[index] = element;return oldValue;}/***  校验index的有效性*  index 不能超过数组元素的个数,否则会报数组越界异常*/
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

在这里插入图片描述

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

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

相关文章

命令行git联网失败,但是实际可以联网

最近下载代码的时候发现总是告诉我连不上github的网页&#xff0c;但是我自己通过浏览器又可以上网&#xff0c;找了半天发现这个方法可以。 记录下这个代理 打开git bash 执行以下命令&#xff1a; git config --global http.proxy http://127.0.0.1:7890 git config --glob…

VLAN笔记

虚拟VLAN 什么是VLAN VLAN的作用 VLAN的优缺点 VLAN的配置方法 VLAN有哪些接口模式 access与trunk接口的区别 Hybrid接口 拓扑实验enspCiscoH3C ​ 什么是VLAN VLAN&#xff08;Virtual Local Area Network&#xff09;又称虚拟局域网&#xff0c;是指在交换局域网的基础上&a…

# 如何使用 GitHub Copilot 发送 Tweet(译)

文章目录 首先&#xff0c;什么是 GitHub Copilot为什么我使用GitHub Copilot 发送 Tweet&#xff1f;理由 1理由 2理由 3 它对你来说有什么价值&#xff1f;如何使用 Copilot 发送推文步骤一&#xff1a;注册 Twitter 开发者账户步骤二&#xff1a;在 Twitter 开发者平台创建应…

HAProxy终结TLS双向认证代理EMQX集群

文章目录 1. 背景介绍2. 系统架构3. 证书签发3.1 创建根证书3.2 创建中间证书3.3 创建设备证书3.4 创建服务端证书 4. HAProxy开启双向认证5. 验证6. 总结 1. 背景介绍 MQTT协议已经成为当前物联网领域的关键技术之一&#xff0c;当前市面上主流的实现MQTT协议的产品主要有 EMQ…

Visio文件编辑查看工具Visio Viewer for Mac

Visio Viewer for Mac(Visio文件编辑查看工具)激活版带给大家&#xff01;Visio Viewer for Mac是一款能够快速的打开Visio文件&#xff0c;包括 vsd 和 vsds 格式的文件编辑查看工具&#xff0c;Visio Viewer mac支持放大和缩小浏览&#xff0c;并可以将Visio绘图文件转换为PD…

Flink提交jar出现错误RestHandlerException: No jobs included in application.

今天打包一个flink的maven工程为jar&#xff0c;通过flink webUI提交&#xff0c;发现居然报错。 如上图所示&#xff0c;提示错误为&#xff1a; Server Response Message: org.apache.flink.runtime.rest.handler.RestHandlerException: No jobs included in application. …

使用SimPowerSystems并网光伏阵列研究(Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

09:STM32-------USART串口通信+串口数据包

目录 一:串口协议 1:通信接口 2:串口通信 3:硬件电路 4:电平标准 5:串口参数及其时序 二:USART介绍 1:简历 2:USART框图 3:USART的基本结构 4:数据帧 5: 波特率发生器 6:数据模式 三:案例 A:串口发送--单发送 1:连接图 2:函数介绍 3:代码 B:串口发送接收 1…

《CTFshow-Web入门》09. Web 81~90

Web 入门 索引web81题解 web82题解原理 web83题解 web84题解 web85题解 web86题解 web87题解原理 web88题解 web89题解 web90题解 ctf - web入门 索引 web81&#xff1a;include() 利用&#xff0c;一句话木马之 Nginx 日志利用。web82~86&#xff1a;include() 利用&#xff…

ES6中let和const关键字与var关键字之间的区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 变量作用域&#xff08;Scope&#xff09;&#xff1a;⭐ 变量提升&#xff08;Hoisting&#xff09;&#xff1a;⭐ 重复声明&#xff1a;⭐ 初始化&#xff1a;⭐ 全局对象属性&#xff1a;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#…

【问题总结】 记 一次dockerFile构建报错

写在前面&#xff0c; 其实是一个比较摸不着脑袋的bug&#xff0c;记录一下解决的过程&#xff0c;作为备忘录 问题留档 1、场景描述 在尝试使用dockefile构建一个tomcat镜像&#xff0c;内容如下&#xff0c;构建正常通过&#xff0c;但是容器启动失败 FROM centos:7 MAINT…

设备管理系统有什么功能?它有什么用?

设备管理系统已成为现代化大规模研究所&#xff0c;信息化管理体系建设中最为关键的要素。随着工业设备的机械化、自动化、大型化、高速化以及复杂化等因素不断叠加&#xff0c;设备设施对于工业生产的作用和影响越来越大&#xff0c;其各项制度和流程也涉及面广、内容繁杂。  …

note_前端框架Vue的安装和简单入门(Windows 11)

1. Vue安装 (1) 下载安装node.js和npm # 下载msi安装包 https://nodejs.org/en# 点击安装包&#xff0c;按提示安装 # 默认安装nodejs, npm, 在线文档; PATH配置# 确认安装是否成功&#xff0c;在dos中输入 node -v # 验证nodejs是否安装成功 npm -v # 验证nodejs包管…

入门力扣自学笔记280 C++ (题目编号:1123)(二分查找)(多看看)

2594. 修车的最少时间 题目&#xff1a; 给你一个整数数组 ranks &#xff0c;表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你一个整数 cars &#xff0c;表示总共需要修理的汽车数目。 请你返…

TLS协议深度解析:挖掘现代网络安全防御的底层技术

正常简单的通讯 1、服务器生成一对密钥&#xff0c;公钥A、私钥A 2、浏览器请求服务器时&#xff0c;服务器把公钥A传给浏览器 3、浏览器随机生成一个对称加密的密码S&#xff0c;用公钥A加密后传给服务器 4、服务器接收后&#xff0c;用私钥A解密&#xff0c;得到密钥S 5、浏…

微信小程序开发---事件的绑定

目录 一、事件的概念 二、小程序中常用的事件 三、事件对象的属性列表 四、bindtap的语法格式 &#xff08;1&#xff09;绑定tap触摸事件 &#xff08;2&#xff09;编写处理函数 五、在事件处理函数中为data中的数据赋值 六、事件传参 七、bindinput的语法格式 八、…

Vue + Element UI 前端篇(十五):嵌套外部网页

Vue Element UI 实现权限管理系统 前端篇&#xff08;十五&#xff09;&#xff1a;嵌套外部网页 嵌套外部网页 在有些时候&#xff0c;我们需要在我们的内容栏主区域显示外部网页。如查看服务端提供的SQL监控页面&#xff0c;接口文档页面等。 这个时候就要求我们的导航菜…

数学建模竞赛常用代码总结-PythonMatlab

数学建模过程中有许多可复用的基础代码&#xff0c;在此对 python 以及 MATLAB 中常用代码进行简单总结&#xff0c;该总结会进行实时更新。 一、文件读取 python (pandas) 文件后缀名&#xff08;扩展名&#xff09;并不是必须的&#xff0c;其作用主要一方面是提示系统是用…

matlab 矩阵逆运算的条件数

目录 一、概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 1、算法概述 条件数法是目前应用最为广泛的一种病态诊断方法。一个方阵…

VsCode备忘

上次简单学习了一下vscode的使用&#xff0c;结果好长时间没用&#xff0c;今天打开又全忘了。。。再记录一下吧 快捷键 CtrlShiftP 命令面板&#xff0c;查找命令&#xff0c;设置等等 Ctrl 打开集成终端&#xff0c;监视生成输出 Ctrl, 打开设置 CtrlP 转到文件,使用转到符…