再探Java集合系列—ArrayList

适用于什么场景?

检索比较多的场景,例如学生成绩管理系统,老师对学生的成绩进行排名或查询操作

ArrayList有哪些特点?

1、ArrayList集合底层采用了数组数据结构,是Object类型

2、动态数组。ArrayList的默认初始容量为10,扩容因子为1.5,数组长度随着容量的增长数组长度。但是数组的长度并不会随着ArrayList的容量立即缩小,除非显示的调用 trimToSize 方法

3、建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略.因为在在扩容的同时需要将原来数组中的数据复制到新数组里,但如果要插入大量数据时,赋值数组的形式效率很低,所以大多数情况下会使用带参构造函数,传入一个预估计容量,提前定义好容量。

4、ArrayList是非线程安全

单独看这些特点我们还是回觉得有些枯燥,结合具体场景我们来分析分析

实战演练

import java.util.ArrayList;
import java.util.List;public class ListTest {
public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("b");//第一个,索引下标0list.add("d");list.add("c");list.add("a");list.add("d"); //允许使用重复元素System.out.println(list);  //输出结果:[b, d, c, a, d]System.out.println(list.get(2));  //输出指定下标的元素,输出结果:clist.add(1,"f");//在指定索引下标位置添加元素System.out.println(list); //输出结果:[b, f, d, c, a, d],原来下标为1和1之后的下标索引位置的元素自动向后移动List<String> a = new ArrayList<String>();a.add("123");a.add("456");list.addAll(2,a);  //在指定索引下标的位置插入集合System.out.println(list);//输出结果:[b, f, 123, 456, d, c, a, d]//获取指定元素在集合中第一次出现的索引下标System.out.println(list.indexOf("d")); //输出结果:4//获取指定元素在集合中最后一次出现的索引下标System.out.println(list.lastIndexOf("d"));//输出结果:7list.remove(2);  //根据指定的索引下标移除元素System.out.println(list);  //输出结果:[b, f, 456, d, c, a, d]list.set(1,"ff"); //根据指定的索引下标修改元素System.out.println(list); //输出结果:[b, ff, 456, d, c, a, d]//根据索引下标的起始位置截取一段元素形成一个新的集合,截取的时候,包含开始的索引不包含结束时的索引List<String> sublist= list.subList(2,4);System.out.println(sublist);//输出结果:[456, d]System.out.println(list.size());//输出结果7}
}
import java.util.LinkedList;
import java.util.List;public class ListTest {
public static void main(String[] args){List l1 = new LinkedList();for(int i = 0;i<=5;i++){l1.add("a"+i);}System.out.print(l1);l1.add(3,"a100");System.out.println(l1);l1.set(6,"a200");System.out.println(l1);System.out.print((String)l1.get(2)+" ");System.out.println(l1.indexOf("a3"));l1.remove(1);System.out.println(l1);}
}

输出结果:

[a0,a1,a2,a3,a4,a5]

[a0,a1,a2,a100,a3,a4,a5]

[a0,a1,a2,a100,a3,a4,a200]

a2 4

[a0,a2,a100,a3,a4,a200]

底层原理

有几个变量在之后增删改查方法中会反复使用,我们需要注意

注意:
  • 数组长度是指当前数组内元素的个数
  • 数组容量是指数组所能容纳的长度

①、序列化和反序列化问题

在方法签名上我们看到ArrayList类实现了Serializable接口,说明我们创建的ArrayList数组可以序列化(存储数据库、传输数据等)和反序列化,但是用于存储元素的数组elementData为什么还用transient关键字修饰呢?我们都知道用transient关键字修饰的变量可以不进行序列化和反序列化,那这样做是为什么呢?

大家设想一个场景:此时我的数组长度为15,但实际元素大小为11,是不是剩余4个空间没有用到?如果我们在序列化和反序列化的时候是不是就要多序列化和反序列化4个空间的内容,是不是浪费了无效的操作?所以秉持着高效第一的原则减少无效操作。在ArrayList的底层有两个方法readObject和writeObject用于序列化

此时我们会发现在遍历的范围是0到实际数组的大小,拿上面的场景来说就是0-10的范围,序列化数组中0-10的元素,这样没有用到的4个空间是不是就没有被序列化和反序列化。

②、添加元素——add()

思想:

创建一个Object类型的空数组(注意:当第一次add添加元素的时候,才指定默认容量为10)

ensureCapacityInternal方法先判断容量值是否大于当前ArrayList的容量,如果大于当前集合容量,则需要调用grow方法进行扩容;反之,不用操作

③、grow扩容——ArrayList扩容机制

ArrayList的使用前不需要像数组一样提前定义大小空间,容量是随着使用时自动增长的,那为什么在使用ArrayList的add方法添加元素的时候底层还需要判断集合的容量是否能够放下要添加的元素呢?又没有定义固定大小直接放进去不就好了吗?

add方法添加分为三步:

①、判断集合容量是否满足添加的元素

②、添加元素

③、集合长度+1

什么时候需要扩容?

如果当前容量+ 1超过数组长度

用户不需要提前定义大小,那是因为底层默认已经定义好了大小。其实是有一个边界值的,并不是无限增长的。使用时增加,是因为底层有扩展因子(扩容因子是1.5),当数量达到数组的百分之多少的时候就会扩容。ArrayList默认的初始大小是10,其实在一开始new完之后的数组容量并不是10,而且一个空的数组,当添加第一个元素的时候会进行第一次扩容,数组容量变为10

ArrayList扩容的时候会将原来的数组复制到一个新的数组中,为什么这么做?那原来的数组什么时候回收?

当 ArrayList 需要扩容时,会创建一个新的更大的数组,并将原来的数组中的元素复制到新数组中。这样做的原因是为了确保数组的连续性,以便能够快速地访问和修改元素。如果不进行数组复制,而是直接在原数组上进行扩容,可能会因为内存不连续而导致性能下降

原来的数组会在扩容后变得多余,不再被使用。原来的数组会在没有任何引用指向它时变为不可达,即没有任何变量指向原数组时,原数组会成为垃圾对象。一旦原数组成为不可达的垃圾对象,垃圾回收器就会在适当的时候将其回收,释放其占用的内存空间。这个过程是由垃圾回收器自动管理的,程序员不需要显式地释放原数组。

④、在指定位置插入新元素——add()

当我们在指定位置插入元素的时候,要插入下标的后面元素会整体向后移动一位,增加了系统额外的系统开销,如上面的图片例子来说:如果要插入位置越靠近数组前面,我们会发现数组的移动变得很大

⑤、更新元素——set()

⑥、删除元素——remove()

不管是删除指定位置元素和直接删除元素都涉及到了数组元素的移动,所以我们要删除的元素如果越靠近数组的前面,所消耗的性能越大

注意:不要遍历集合删除元素,会出现数据不一致问题,个别元素没有删除成功

⑦、查找元素——indexOf()

因为数组有一个特点是可以根据下标查找元素,如果按照指定下标查找元素,ArrayList的性能会很高,但是根据上图的源码我们不难发现:如果是根据元素查找下标,会从头到尾遍历整个数组,如果数组的位置特别靠近末尾,那整个查询会非常耗时


出现的问题:

线程安全问题:当多线程环境下同时对集合操作(添加、删除、修改元素),可能导致数据不一致问题(数组越界、数据丢失等)

解决方案:

  1. 使用CopyOnWriteArrayList线程安全集合
  2. 使⽤ Collections.synchronizedList 包装 ArrayList,然后操作包装后的 list


CopyOnWriteArrayList

CopyOnWrite — —写时复制

读操作是⽆锁的,性能较⾼;写操作的时候先将当前容器复制一份,然后在新数组上执行写操作,结束之后再将原容器的引用指向新容器

参考网上图片

备:参考网上图片

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

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

相关文章

第1章 理解知识图谱(一)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

SpringBoot : ch11 整合RabbitMQ

前言 在当今的互联网时代&#xff0c;消息队列成为了构建高可靠、高性能系统的重要组件之一。RabbitMQ作为一个可靠、灵活的消息中间件&#xff0c;被广泛应用于各种分布式系统中。 本篇博客将介绍如何使用Spring Boot整合RabbitMQ&#xff0c;实现消息的发送和接收。通过这种…

视频集中存储/磁盘阵列EasyCVR平台黑名单异常解决步骤是什么?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

华为云(HECS)docker环境下安装jenkins

Jenkins是一个开源的自动化工具&#xff0c;可以自动化地完成构建、测试、交付或部署等任务。总之重点就是三个字&#xff1a;自动化&#xff0c;至于如何实现这些功能&#xff0c;Jenkins基于插件化的机制&#xff0c;提供了众多的插件来完成持续集成CI与持续部署CD。 【持续…

【Python】python天气数据抓取与数据分析(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

pdf文件能扫码查看吗?一键做文本二维码

pdf格式是常用的一种文件格式&#xff0c;很多资料、展示性的内容都会选择这种格式&#xff0c;现在很多人都需要将文件生成二维码图片后分享给他人&#xff0c;那么文件存入二维码展示的方法有哪些呢&#xff1f;下面给大家分享一招使用二维码生成器来生成二维码图片的操作方法…

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…

SVD recommendation systems

SVD recommendation systems 为什么在推荐系统中使用SVD 一个好的推荐系统一定有小的RMSE R M S E 1 m ∑ i 1 m ( Y i − f ( x i ) 2 RMSE \sqrt{\frac{1}{m} \sum_{i1}^m(Y_i-f(x_i)^2} RMSEm1​i1∑m​(Yi​−f(xi​)2 ​ 希望模型能够在已知的ratings上有好的结果的…

[学习笔记]IK分词器的学习

IK分词器有几种模式 # 测试分词器 POST /_analyze {"text":"黑马程序员学习java太棒了","analyzer": "standard" }# 测试分词器 POST /_analyze {"text":"黑马程序员学习java太棒了","analyzer": &quo…

怎么更新BI报表数据?问我就对了

BI大数据分析工具上有大量的BI报表模板&#xff0c;这些模板都是一个个完整的BI报表&#xff0c;只需将数据源更换&#xff0c;立即就能用来分析我们自己的数据。那&#xff0c;BI报表的数据怎么更新&#xff1f;接下来就来说说这事。 目的&#xff1a;更新BI报表数据 工具&a…

第3章 表、栈和队列

前言 本章讨论最简单和最基本的三种数据结构。实际上&#xff0c;每一个有意义的程序都将至少明确使用一种这样的数据结构&#xff0c;而栈则在程序中总是隐含使用&#xff0c;不管你在程序中是否做了声明。 在这一章&#xff0c;我们将&#xff1a; 介绍抽象数据类型…

Pikachu靶场(PHP反序列化漏洞)

查看php反序列化漏洞的概述&#xff0c;了解序列化与反序列化。 构造payload <?php class S{var $test "<script>alert(wjy)</script>"; } $c new S(); echo(serialize($c)); ?>将对象序列化为O:1:"S":1:{s:4:"test";s:…

【Linux下基本指令——(1)】

Linux下基本指令——&#xff08;1&#xff09; 一. ls 指令1.1.语法&#xff1a;1.2.功能&#xff1a;1.3.常用选项&#xff1a;1.4.举例&#xff1a;1.5.Xshell7展示 二. pwd 命令2.1.语法: 2.2.功能&#xff1a;2.3.常用选项&#xff1a;2.4.Xshell7展示 三. cd 指令3.1.语法…

选择跨网数据摆渡系统时,你最关注的功能是哪些?

为什么要选择跨网数据摆渡系统呢&#xff1f;因为做了网络隔离后&#xff0c;要有数据交互。那为什么要做网络隔离呢&#xff1f;主要还是安全方面的考虑&#xff0c;一般有以下几个原因&#xff1a; 1、数据安全保护&#xff1a;对于一些重要数据&#xff0c;比如代码数据、隐…

hutool工具连接数据库实现数据处理重新入库

1 引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.18</version></dependency><!--mysql驱动包--><dependency><groupId>mysql</groupId><ar…

Python语言学习笔记之四(Python文档化)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 Python文档化是指在Python代码中添加注释和文档字符串&#xff0c;以提供有关代码的详细信息和说明。 文档的…

P24 C++ 字符串常量

前言 本期我们讨论字符串字面量。 这是一种基于字符串的东西&#xff0c;上一章我们讲过字符串&#xff0c;你一定要去先去看看那一期的内容。 P23 C字符串-CSDN博客 01 什么是字符串字常量呢&#xff1f; 字符串字面量就是在双引号之间的一串字符 在上面的代码中&#xf…

Unity针对XBOX,SWITCH,PS5手柄的适配踩坑

前言&#xff1a; 记录一点最近在做手柄适配问题的踩坑。 这里推荐一款Unity做手柄适配的插件->Rewired Rewired官方文档链接Rewired Documentation | Supported Controllers Rewired插件里面有个是Player类&#xff0c;这个类获取到当前玩家的输入设备&#xff0c;输入…

酷开系统 | 追求娱乐不止一种方式,酷开科技带你开启新体验!

在当今社会&#xff0c;娱乐方式多种多样&#xff0c;人们对于娱乐的需求和追求也在日益增长。然而&#xff0c;传统的娱乐方式已经无法满足大家对于多元化、个性化的体验需求。此时&#xff0c;酷开科技以其独特的视角和领先的技术&#xff0c;为消费者们带来了全新的娱乐体验…

【数据结构 —— 二叉树的链式结构实现】

数据结构 —— 二叉树的链式结构实现 1.树的概念及其结构1.1.树概念1.2.树的结构1.3树的相关概念1.4.树的表示1.5. 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树的概念及其结构2.1二叉树的概念2.2.现实中的二叉树&#xff1a;2.3. 特殊的二叉树…