夺命追问带你深入了解ArrayList与LinkedList

news2025/1/12 20:55:50

目录

一、ArrayList

问题1:说一下JDK1.7与1.8 ArrayList有什么区别?

问2:说一下ArrayList的扩容机制?

问3:下面这段代码会将数组扩容到多少?

问4:说说迭代器Iterator的两种规则:fail-fast和fail-safe

问5:简单说说fail-fast的源码

二、LinkedList

问1:ArrayList与LinkedList的比较?

问2:ArrayList与LinkedList 查询、增删性能比较?

问3:什么是局部性原理?


一、ArrayList

问题1:说一下JDK1.7与1.8 ArrayList有什么区别?

答:

(1)JDK7:JDK7的ArrayList类似于单例模式的饿汉式,new ArrayList();时就创建长度为10的数组,当你添加到第11个元素时进行数组的扩容,扩容成当前的1.5倍,也就是长度15的数组。

(2)JDK8:JDK8的ArrayList类似于单例模式的懒汉式,new ArrayList();仅仅是将elementData初始化为{},也就是一个长度为0的数组。当第一次add()的时候,底层才创建了长度为10的数组。添加到第11个元素时数组进行扩容,扩容成当前的1.5倍,也就是长度15的数组。

问2:说一下ArrayList的扩容机制?

答:一开始new ArrayList(); 底层创建容量为0的数组,当我们add()第一个元素时,触发第一次扩容,将容量0变为10,当数组容量存满10个,add()第11个元素时触发第二次扩容,扩容为原理的1.5倍,也就是15。当我们add()到第16个元素,触发第三次扩容,但注意,扩容1.5倍并不准确,拿第三次扩容举例,15 * 1.5 = 22.5,结果发现带小数了。其实底层运用的是移位,15 >> 1(15右移一位,相当于除2)等于7,结果7再加上原始的容量15,就是新的容量,结果是22。

问3:下面这段代码会将数组扩容到多少?

ArrayList<Integer> list = new ArrayList<>();
list.addAll(List.of(1,2,3,4,5,6,7,8,9,10,11));

答:正常我们分析一开始初始化容量是0,一口气放11个元素,那肯定扩容成15了。但结果却是11,为什么呢?因为addAll运行的规律是,当你原始的容量不够时,它会在下一次扩容容量的大小(10,第一次是0,下一次就是10)和我们addAll的元素个数二者之间取一个较大值作为下一次容量。那就是10和11比,那结果就是11。

这就是addAll方法特殊的运行规律。

问4:说说迭代器Iterator的两种规则:fail-fast和fail-safe

答:我们都知道,ArrayList使用迭代器或foreach遍历时,其它线程不可改变其元素,如果改变则会抛出异常,而List其实有两种规则来指定此问题。

fail-fast:一旦发现遍历的同时其它人来修改,则立刻抛异常。

fail-safe:发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性让整个遍历运行完成。例如遍历有5个元素的集合,分别是abcde,假如遍历到c的时候,我们添加一个f,但发现并不会报错,只是结果还是旧的abcde,但是当你整个for循环遍历完成,再打印一次list,则会发现有f元素了。

ArrayList是fail-fast的典型代表,遍历的同时不能修改,尽快失败抛出异常。

CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离。底层就是弄两个数组,一个是用来读的,另一个新数组是用来写新元素的。

问5:简单说说fail-fast的源码

答:foreach遍历集合,其实是走的Iterator,首先判断hasNext(),如果没有了则终止循环,否则next()获取元素时,都要check一下集合元素个数是否变化了,如果变化了,则抛出异常。

modCount是集合添加元素、删除元素的次数,expectedModCount是预期的修改次数。增删操作会使得modCount+1,不等于expetedModCount,所以抛出异常。

没有使用list.iterator时调用的是ArrayList自己的remove方法,并不会同步这两个值,导致抛出异常。调用了ArrayList.iterator之后,此后再remove,remove方法中有让这两个值相等的操作。

迭代器的remove方法会修改expectedModCount,从而使modCount与之相等。

也就是说,如果你要在迭代中移除元素,那你最好用迭代器,别用foreach,因为foreach用list.remove();会报错,但迭代器中的iterator.remove();不会报错。

注意:

expectedModCount:预期修改次数,集合假如一开始add() 5个元素,那expectedModCount就是5。

modCount:在循环的时候如果增删,这个变量就会+1。

二、LinkedList

问1:ArrayList与LinkedList的比较?

答:

(1)ArrayList

① 底层是数组,需要连续内存。

② 随机访问快(指根据下标访问)。

③ 尾部插入、删除性能可以,其它部分插入、删除会移动数据,因此性能会低。

④ 可以利用cpu缓存,局部性原理。

(2)LinkedList

① 底层是双向链表,无需连续内存。

② 随机访问慢(要沿着链表遍历)。

③ 头尾部插入删除性能高。

④ 占用内存多。

解释链表为何查询慢:因为每个节点只知道上一个元素和下一个元素是什么,我如果想找到4,那我从1开始找,1下一个元素是2,2下一个元素是3,3下一个元素是4这么找,沿着链表遍历。

而删除为什么快,双向链表可以想象成小朋友手拉手站成一排,如果其中一个小朋友退出游戏,那么直接让旁边两个小朋友拉手即可,跟其他的小朋友没关系。删除第3个元素,只跟第2个和第4个元素有关系,只需要把第2个元素的地址值给第4个,再将第4个元素的地址值给第2个即可。

问2:ArrayList与LinkedList 查询、增删性能比较?

答:放弃ArrayList增删慢,查询快。LinkedList增删快,查询慢这种说法。

首先我们要明确一个概念,什么是随机访问?什么是查询?

随机访问:是随便根据索引去挑一个。

查询:根据元素的内容去元素里找。

按查询来说,ArrayList和LinkedList 根据元素内容去找的话,时间复杂度都差不多,都是O(n)。换句话说,这哥俩都不太适合用来做查询,要真想做查询,就用HashMap或者TreeMap这种高效的数据结构,它们的哈希表和红黑树都更适合用来做查询。

按随机访问来说,ArrayList肯定比LinkedList要快,因为带索引查询,而双向链表只能沿着链表一点点找。

增删的话,ArrayList尾部增删都很快,因为不用移动其它元素,直接增加或删除即可,如果其它位置增删,比如头部增删,那其它的元素都需要移动位置来配合你。而LinkedList只是头尾插入删除性能比较高,但链表的中间去增删元素,它的性能实在不敢恭维。测试代码结果是LinkedList比ArrayList耗费时间多出6倍。

为什么LinkedList中间插入元素会那么慢?

原因是你需要先定位到中间,那必然通过LinkedList的next一个个的找到中间位置元素,这个查找的过程,移动指针的过程是非常慢的。当然定位到了以后做增删操作是非常快的,无非是链表的指针重新改变一下。

问3:什么是局部性原理?

答:局部性原理就是我们查询一个元素时,可以将此数据相邻的几个元素都加入到缓存中,因为它会做出一种假设,如果你查询的是1,那么你有可能马上会用到2、3、4、5,那我就一起都放入缓存中。因为ArrayList底层是数组,所以它可以实现这种局部性原理的方式来优化。但LinkedList不行,因为双向链表的缘故,有可能你查询的1元素,它的指针指向下一个元素是2,但有可能1和2元素并不相邻,中间可能差的有点远。所以缓存中并没有把你接下来的元素2加载进缓存中。

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

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

相关文章

如何理解线程池中的参数设计

如何理解线程池中的参数设计 你的线程池的参数怎么配置&#xff1f;线程数量设置多少合理&#xff1f;如何确定一个线程池中的人物已经完成了为什么不建议使用java自带的Executors创建线程池线程池里面的阻塞队列设置多少合理&#xff1f; 考察&#xff1a;了解你对技术的掌握…

如何查看SSL证书到期时间

随着互联网的发展&#xff0c;SSL证书在保护网站安全方面发挥着越来越重要的作用&#xff0c;SSL证书过期之后带给网站的损失也比较大&#xff0c;许多网站管理员可能会忽略SSL证书到期的问题&#xff0c;从而导致网站安全漏洞。今天&#xff0c;就随SSL盾小编了解有什么办法查…

开源云真机平台-Sonic平台-python自定义脚本(持续更新中)

开源云真机平台-Sonic平台-python自定义脚本(持续更新中) 1、获取设备序列号 udId sys.argv[1:][1] 如&#xff1a; # -*- coding: utf-8 -*- import os,sys,json udId sys.argv[1:][1] 2、获取全局参数 text1 sys.argv[1:][2] 如&#xff1a; # -*- coding: utf…

邻接矩阵、可达性矩阵、完全关联矩阵、可达性矩阵的计算

邻接矩阵&#xff1a;很简单&#xff0c;就是两个点有关系就是1&#xff0c;没有关系就是0 可达性矩阵&#xff1a;非常简单&#xff0c;两点之间有路为1&#xff0c;没有路为0 可发行矩阵的计算&#xff1a;有n个元素&#xff0c;初始可达性矩阵为A&#xff0c;那么最终的矩阵…

【软考中级-软件设计师】day7:图

概述 1-2道选择 图的存储 图的遍历 图的最小生成树 prim算法 kruscal算法 2135476 选7不选6是因为4的先删除&#xff08;vi必须在vj之前&#xff09;跟4有关的删了以后&#xff0c;入度为0的结点只剩下7&#xff0c;所以选7再6

烟火识别系统、ai视频烟火自动报警系统、烟火监测系统、烟火报警

在现代社会&#xff0c;烟火&#xff08;包括烟雾和火焰&#xff09;的及时识别对于防灾减灾具有至关重要的作用。随着人工智能技术的发展&#xff0c;基于AI的视频烟火识别系统已成为研究和应用的热点。这些系统通过分析监控视频&#xff0c;能够迅速准确地识别出烟雾和火焰&a…

内存分区模型---C++

目录 内存分区模型1.1 程序运行前1.2 程序运行后1.2.1 new操作符 内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的&#xff1b;全局区&#xff1a;存放全局变量和静态变量以及常…

SpringBoot学习(五)-Spring Security配置与应用

注&#xff1a;此为笔者学习狂神说SpringBoot的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! Spring Security Spring Security是一个基于Java的开源框架&#xff0c;用于在Java应用程…

网络传输(TCP)

前言 我们tcpdump抓包时会看到除报文数据外&#xff0c;前面还有一段其他的数据&#xff0c;这段数据分为两部分&#xff0c;ip包头&#xff08;一般20字节&#xff09;和tcp包头&#xff08;一般20字节&#xff09;&#xff0c;一般这两个头长度和为40&#xff0c;我们直接跳…

HarmonyOS 开发基础(九)forEach

HarmonyOS 开发基础&#xff08;九&#xff09;forEach 一、基础使用 Entry Component struct Index {// 创建一个变量&#xff0c;用来存储图片网络网址imageUrl: string https://gw.alicdn.com/imgextra/i2/2201227850912/O1CN01B7gVvP1Ibk6HMiDRz_!!2201227850912.jpg_Q75.…

父类,父类的分类、子类 同时重写方法,调用问题

做一个猜想&#xff0c;当父类、父类的分类、子类 都写了同一个方法&#xff0c;那么在调用的时候会优先调用哪个方法呢&#xff1f;我们可以先写一个简单的demo implementation Person (void)test {NSLog("%",self.class); }end然后是Person的分类 #import "…

git 使用 submodule 如何指定分支

写在前面, 作为一个前端我是不喜欢使用 submodule的, 我更喜欢 npm 包的管理方式。 首次添加子模块 git submodule add -b <branch> <remote> <path> 不指定分支就不传 -b <branch> <branch> 分支名<remote> 仓库地址<path> 子模块…

零样本学习研究方向sci四区期刊总结

APPLIED OPTICS sci 四区 非OA 出版商:OPTICA 期刊官方网站: http://www.opticsinfobase.org/ao/home.cfm 期刊投稿网址: http://www.opticsinfobase.org/ao/journal/ao/author.cfm#submit 虽然有zsl的名字但是这是全息图像专刊&#xff0c;跟我的方向应该不是太相关。 MO…

C# 微信小程序获取群id

前提 有个需求&#xff0c;需要限制小程序的抽奖只能在某个群内&#xff0c;需要知道谁在群里面&#xff0c;但是微信并没有提供谁在群里面的方法&#xff0c;不过提供了获取群id的方法&#xff0c;这样加上限制分享就能保证群里的参加&#xff0c;即时分享出去了&#xff0c;…

网络层详解

目录 前言 一、IP协议 1、IP协议报头 2、协议字段理解 &#xff08;1&#xff09;4位版本 &#xff08;2&#xff09;4位首部长度 &#xff08;3&#xff09;8位服务类型 &#xff08;4&#xff09;16位总长度 &#xff08;5&#xff09;标识、标志与片偏移 &#xf…

【自控实验】2. 采样控制系统特性

本科课程实验报告&#xff0c;有太多公式和图片了&#xff0c;干脆直接转成图片了 仅分享和记录&#xff0c;不保证全对 通过对二阶连续系统、二阶采样系统和具有保持器的二阶采样系统仿真实验&#xff0c;比较三种系统的特性&#xff0c;加深对采样控制系统的了解 使用matl…

阿里云OSS上传视频,可分片上传

uniappH5实现 阿里云OSS上传视频 示例图&#xff1a; 上传视频完整示例代码&#xff1a; 使用npm安装SDK开发包&#xff0c;安装命令为 npm install ali-oss --save accessKeyId 和 accessKeySecret 还有 bucket 替换成你的就行。 multipartUpload 的第一个入参是&#x…

leetcode 动态规划(最后一块石头的重量II、目标和、一和零)

1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度&#xff1a;中等 有一堆石头&#xff0c;每块石头的重量都是正整数。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < …

Redis的设计、实现

数据结构和内部编码 type命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)hash(哈希)、list(列表)、set(集合)、zset (有序集合),但这些只是Redis对外的数据结构。 实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的…

动手学深度学习-卷积神经网络

卷积神经网络 在前面的章节中&#xff0c;我们遇到过图像数据。这种数据的每个样本都由一个二维像素网格组成&#xff0c;每个像素可能是一个或者多个数值&#xff0c;取决于是黑白还是彩色图像。到目前为止&#xff0c;我们处理这类结构丰富的数据方式还不够有效。我们仅仅通…