再探Java集合系列—ArrayList

news2024/11/24 0:37:03

适用于什么场景?

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

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");//第一个,索引下标0
    list.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));  //输出指定下标的元素,输出结果:c
    
    list.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"));//输出结果:7
    
    list.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.coloradmin.cn/o/1265798.html

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

相关文章

【模电】单结晶体管

单结晶体管 单结晶体管的结构和等效电路工作原理和特性曲线应用举例 单结晶体管的结构和等效电路 在一个低掺杂的N型硅棒上利用扩散工艺形成一个高掺杂P区&#xff0c;在P区与N区接触面形成PN结&#xff0c;就构成单结晶体管(UJT)。其结构示意图如下图所示。   P型半导体引出…

【SK-learn学习】1.16 概率校准

一、说明 概率校准&#xff0c;指的是对于分类器而言&#xff0c;对应多种类别&#xff0c;概率最大就将样本归入&#xff0c;这个事实没有考虑置信度的问题。sklearn的calibration就是指的这种情形&#xff0c;参考本文。 二、关于sklearn.calibration的概念 执行分类时&#…

Java如何有效避免空指针

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 hello&#xff0c;伙伴们&#xff0c;shigen又来了&#xff0c;这篇稿子是周末的时候写出来的。今天的话题是&a…

Linux处理文本常见命令

目录 1 vim 2 echo 3 tee 4 cat 1 vim 编辑文本类的内容&#xff0c;使用的时候 vim [文件名]&#xff0c;比如 vim A.txt 进入vim界面后&#xff0c;按i可以开启编辑模式&#xff0c;按ESC可以关闭编辑模式&#xff0c;关闭编辑模式后:wq!保存并退出 2 echo ech…

SSM项目管理系统开发oracle10g数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM项目管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统 主要采用B/S模式开…

【九】linux下部署frp客户端服务端实践

linux下部署frp客户端服务端实践 简介&#xff1a; 今天有一个这样的需求&#xff0c;部署在公司内部局域网虚拟机上的服务需要在外网能够访问到&#xff0c;这不就是内网穿透的需求吗&#xff0c;之前通过路由器实现过&#xff0c;现在公司这块路由器不具备这个功能了&#x…

科东快讯 | Intewell鸿道工业操作系统亮相丝路峰会暨中国新工业博览会

近日&#xff0c;第十六届中国工业论坛丝路峰会暨中国新工业博览会在陕西西安临空会展中心圆满落幕&#xff01; 峰会围绕“十四五”工业经济高质量发展的新使命、新担当&#xff0c;以“新工业新动能新格局”为主题&#xff0c;聚焦新兴工业领域&#xff0c;突出新工业产业集…

想学计算机视觉入门的可以看过来了

文章写了有一段时间了&#xff0c;期间不少小伙伴来咨询如何自学入门AI&#xff0c;或者咨询一些AI算法。 90%的问题我都回复了&#xff0c;但有时确实因为太忙&#xff0c;没顾得过来。 在这个过程中&#xff0c;我发现很多小伙伴问的问题都类似&#xff1a;比如如何入门计算…

一名技术Leader应该是创作者

今天看了一本书叫做《黑客与画家》。它里面提到一个很重要的概念就是黑客&#xff08;优秀的程序员&#xff09;是一名建筑师&#xff0c;而不是一名工程师。 传统的主管和互联网的Leader 这两者有什么区别呢&#xff1f;关键点在于建筑师是思考做什么&#xff0c;而工程师是…

浅析函数防抖节流

防抖和节流都是前端开发中常用的优化性能的技术。 一、定义 防抖&#xff1a; 防抖指的是在事件触发后&#xff0c;在规定的时间内若再次触发&#xff0c;则重新计时&#xff0c;直到规定时间内没有再次触发事件&#xff0c;才执行事件处理。这样可以避免在短时间内频繁地触发…

信创实时云渲染,Paraverse平行云LarkXR适配多个国产操作系统

近日&#xff0c;Paraverse平行云企业级实时云渲染解决方案LarkXR&#xff0c;现已全面支持国产信创技术路线。这一进展不仅是3D/XR领域国产软件领域在操作系统兼容的一次进步&#xff0c;也是对国家自主创新战略的有力响应&#xff0c;展示了Paraverse平行云在推动国产软件发展…

从戴森发明的“球轮手推车”看专利

今天跟大家分享一个特别有意思的专利&#xff0c;那就是戴森发明的球状轮子的手推车。 相信戴森这个品牌很多人都听过&#xff0c;大家熟悉的应该是戴森吹风机和戴森吸尘器。这两个目前是市场上比较高端的家用设备。 很多人也正是因为这些家用设备了解到戴森这个人&#xff0…

第二十章 -----多线程

20.1 线程简介 计算机完全可以将多种活动同时进行&#xff0c;这种思想在java中称为并发&#xff0c;将并发完成的每一件事情称为线程 线程的特点&#xff1a; 极小的单位 一个进程有很多个线程 线程共享进程的资源 20.2 创建线程 20.2.1 继承Thread类 Thread类是Java.l…

EC 404 information economics

EC 404 information economics WeChat: zh6-86

九、LuaTable(表)

文章目录 一、定义二、Table(表)的构造三、Table 操作&#xff08;一&#xff09;Table连接&#xff08;二&#xff09;插入和移除&#xff08;三&#xff09;Table 排序&#xff08;四&#xff09;Table 最大值 一、定义 table 是 Lua 的一种数据结构用来帮助我们创建不同的数…

MySQL -DDL 及表类型

DDL 创建数据库 CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification:[DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 1.CHARACTER SET&#xff1a…

sqli-labs(6)

27. 过滤了union和select 使用双写绕过 有报错信息使用报错注入 1and(extractvalue(1,concat(0x5c,database())))and11 1and(updatexml(1,concat(0x7e,database(),0x7e),1))and11 1and(extractvalue(1,concat(0x5c,(selseselectlectect(group_concat(table_name))from(inform…

MIT线性代数笔记-第17讲-正交矩阵,Schmidt正交化

目录 17.正交矩阵&#xff0c; S c h m i d t Schmidt Schmidt正交化打赏 17.正交矩阵&#xff0c; S c h m i d t Schmidt Schmidt正交化 “标准”经常表示单位长度 标准正交基&#xff1a;由两两正交的单位向量组成的基 将标准正交基中的元素记作 q ⃗ 1 , q ⃗ 2 , ⋯ , q …

【算法】20231128

这里写目录标题 一、55. 跳跃游戏二、274. H 指数三、125. 验证回文串 一、55. 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&am…

面试题:汉诺塔问题 · 递归

你好&#xff0c;我是安然无虞。 文章目录 汉诺塔问题问题描述解题思路代码详解 汉诺塔问题 问题描述 解题思路 这道题的名字还是很响的&#xff0c;基本上都能看出来使用递归解题&#xff0c;但是具体怎么实现还是需要细细想一想。 我们一步一步来&#xff0c;请看&#xff…