再探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,一经查实,立即删除!

相关文章

Makefile之 CFLAGS CXXFLAGS CC LDFLAGS LD

Makefile之 CFLAGS CXXFLAGS CC LDFLAGS LD 1&#xff09;CFLAGS&#xff1a;该环境变量用于指定C语言的编译器选项。它包含了编译C源代码时所需的选项&#xff0c;例如优化级别、警告级别、编译器标志等。在Makefile中&#xff0c;可以将CFLAGS设置为任何所需的编译器选项。例…

BSD socket API

API函数 以下函数是最基本的 socket API socket() 创造某种类型的套接字&#xff0c;分配一些系统资源&#xff0c;用返回的整数识别。 bind() 一般是用在服务器这边&#xff0c;和一个套接字地址结构相连&#xff0c;比如说是一个特定的本地端口号和一个IP地址。 listen()用在…

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

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

SpringBoot : ch11 整合RabbitMQ

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

基于SpringBoot的教师工作量管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的教师工作量管理系统,ja…

硬件工程师助理怎么买器件

1. 买器件的要求。 1. 保证器件的质量。 2. 货期近可能的合适。 3.稳定渠道。 2. 器件到哪里买 1. 首先到立创商城去买&#xff0c;因为方便&#xff0c;价格明显&#xff0c;质量有保证。 立创商城链接&#xff1a;新人注册享元器件优惠券_新人特价商品_立创商城 (szlcsc.com…

视频集中存储/磁盘阵列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…

每日随想:在Python中字符串如何转列表

要将一个字符串转换为列表&#xff0c;可以使用字符串的 split() 方法。split() 方法根据指定的分隔符将字符串分割成子串&#xff0c;并将这些子串组成列表。 假设有一个用空格分隔的字符串&#xff0c;我们想要将其转换为一个单词列表&#xff1a; my_string "Hello w…

Ajax 是什么? 如何创建一个 Ajax?

Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种使用客户端JavaScript发送异步HTTP请求到服务器的技术&#xff0c;以便在不重新加载整个页面的情况下更新部分网页内容。 使用Ajax的原因有很多&#xff0c;以下是其中一些&#xff1a; 改善用户体验&…

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

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

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

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

插入排序:理解经典的排序技术

什么是插入排序&#xff1f; 插入排序是一种简单而直观的排序算法&#xff0c;它的工作方式类似于我们手动排序卡片或整理文件&#xff1a; 工作原理&#xff1a; 初始状态&#xff1a;将数组分为两部分&#xff1a;一部分是已排序的&#xff08;最开始时这部分只包含第一个元…

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…

电脑风扇转一下停一下,无法正常开机问题解决

今天同事电话说电脑开不了机了&#xff0c;只听见风扇不停地呜呜地作响。笔者第一反应是不是硬件哪里出问题了&#xff0c;于是二话没说拿起心爱的螺丝刀就闪了过去。 按下电源&#xff0c;确实如电话所述。但感觉风扇并非一直在转&#xff0c;而是时断时续。由于听不大真切&a…

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

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

第3章 表、栈和队列

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

每日OJ题_算法_双指针⑧力扣18. 四数之和

目录 力扣18. 四数之和 解析代码 力扣18. 四数之和 难度 中等 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&…