JAVASE进阶:Collection高级(2)——源码剖析ArrayList、LinkedList、迭代器

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助

ArrayList的底层其实还是比较复杂的,如果你去尝试阅读源码的话,但是这又是面试常考的问题,网上有些面经会说当创建ArrayList的时候会在底层创建长度为10的数组,后续会以1.5倍扩容,但是实际上这既不完整,也不准确。因为ArrayList除了用add方法添加元素,还有用addAll方法添加元素。
在这里剖析一下ArrayList,另外剖析一下LinkedList和迭代器的底层。

源码剖析ArrayList、LinkedList

  • ArrayList
    • 原理
    • 源码剖析ArrayList
  • LinkedList
    • LinkedList方法
    • 源码剖析LinkedList
  • 源码剖析迭代器

ArrayList

原理

新手还是先大致讲解一下原理有个印象再去看源码会比较好:

1、利用空参创建的集合,在底层创建一个默认长度为0的数组elementData,并用size=0记录元素个数/元素下次存入位置
2、添加第一个元素时,底层会创建一个新的长度为10的数组,把元素添加进去,size=1
3、存满时,将会扩容1.5倍(其实就是直接再创建一个长度15的数组并把之前数组的元素拷贝进来)
4、若一次添加了多个元素,扩容1.5倍还是放不下,则创建新数组的长度以实际为准

注意点:

1、创建ArrayList的时候底层默认长度为0,添加第一个元素时才会创建出长度为10的数组
2、不是每次存满都扩容1.5倍,因为ArrayList添加元素的方法还有addAll,添加元素过于多就要以实际为准

源码剖析ArrayList

1、Ctrl+N输入ArrayList:
在这里插入图片描述
2、进入后就是ArrayList的空参构造,定位一下DEFAULTCAPACITY_EMPTY_ELEMENTDATA可以发现其一开始创建的确实是空集合,并且size初始值为0:
在这里插入图片描述
在这里插入图片描述
3、Alt+7找到这个类下面的所有成员变量、方法:
在这里插入图片描述
4、右边栏点击add方法,可以看到它又调用了另一个add方法,传入3个参数(当前要添加的元素、集合底层的数组名、集合长度/当前元素应存入的位置):
在这里插入图片描述
5、跟踪这个add方法,可以看到,在添加元素之前会先判断这个容器满了没有,如果满了就会调用grow()函数扩容,然后再增加元素并更新size。
在这里插入图片描述
6、跟踪grow()方法,它会将size+1传入调用另一个grow做扩容,所以当添加了第一个元素的时候,方法体就是return grow(1)
在这里插入图片描述
7、接着跟踪grow,可以看到函数体先记录了原来数组的老容量,然后进行逻辑判断,若是第一次添加元素,老容量还是0,因此会走else:
先比较了DEFAULT_CAPACITY(10)和minCapacity谁更大,若minCapacity更大则以此为标准,但是由于这里是用add方法而不是addAll方法批量添加,在这里显然是创建了长度为10的数组:
在这里插入图片描述
8、下一次走到该扩容函数的时候,长度是已经大于10了,参数符合if条件,此时oldCapacity=10,minCapacity=11,进入if条件,调用了newLength方法去计算新容量的大小,传入的参数为(老容量,最小扩容长度、老容量/2)
在这里插入图片描述
9、跟踪进入newLength方法,minGrowth和prefGrowth可以视为至少要新增的容量大小默认新增容量大小,取最大值后加上oldLegth,默认的情况即为老容量 + 老容量/2 = 1.5 * 老容量,若minGrowth更大,说明默认的1.5倍扩容还是不够大,这时候容量大小就需要以实际长度为准了,这种情况会出现在ArrayList的addAll函数中:
在这里插入图片描述
10、若不满足if情况,则说明此时超过了限定的大小,就需要用hugeLength函数进行处理,跟踪查看该函数,基本上只会执行else if,返回最大限定值:
在这里插入图片描述
至此,底层源码剖析完毕,其实这部分的原理跟之前的StringBuilder有点像,只不过StringBuilder一创建的默认长度就是16,扩容的时候是以原容量 * 2 + 2的方式进行的,也同时拥有grow()方法中的预防机制即插入元素数量过多,导致长度超过扩容后大小,则容量以实际长度为准,所以阅读源码起来感觉很熟悉。

LinkedList

LinkedList方法

LinkedList底层数据结构是双向链表,查询慢但是增删快,操作首尾元素的速度极快。
每一个结点都有3个部分:前一个结点地址值、当前结点的value、后一个结点的地址值。LinkedList本身就多了很多直接操作首尾元素的特有API。

函数名
public void addFirst(E e)
public void addLast(E e)
public E getFirst()
public E getLast()
public E removeFirst()
public E removeLast()

这些方法全部都是针对首尾的,但是List类都是具有根据索引添加、修改和删除元素的操作的,上面的一些操作相对使用的会比较少。

源码剖析LinkedList

1、Ctrl+N选中LinkedList函数,进入:
在这里插入图片描述
2、查看LinkedList中的内部类Node,这就表示了链表中的每个结点:
在这里插入图片描述
3、找到add方法,其调用了addLast函数,前面讲过addLast是专门封装的给LinkedList操作尾部的API:
在这里插入图片描述
4、跟踪LinkedList,可以看出,LinkedList除了插入元素的时候需要把之前末尾结点中的next赋值新增结点,把新增结点的pre赋值为前一个尾结点,还单独记录了头结点和尾结点的位置,其实就是头插法+尾插法
在这里插入图片描述

源码剖析迭代器

一般是这么使用迭代器的:

ArrayList<String> list = new ArrayList<>();
Iterator<String> it = new list.Iterator();
while(it.hasNext()){String str = it.next();System.out.println(str);
}

接下来跟踪器源码:
1、跟踪Iterator方法,其底层是创建了迭代器对象(Itr)并返回:
在这里插入图片描述
2、跟踪进入Itr,其实它就是一个ArrayList的内部类而已(我们创建的迭代器就是ArrayList对象的迭代器):
(1)cursor表示游标,也就是迭代器中的指针,空参构造默认初始化为0,因此迭代器对象创建后,指针默认指向0索引
(2)lastRet表示之前操作的索引的位置
(3)expectedModCount与并发修改异常有关系
(4)hasNext判断是否有下一个元素(迭代完了没有)
(5)next会获取当前元素并且将指针移动一次
(6)checkForComodification是一种安全性检测
在这里插入图片描述
3、跟踪进入checkForComodification方法,其中modCount变量,这个变量的意思是集合变化的次数,因为删除、修改和添加元素都会使得modCount+1。
这个函数体中先判断当前集合中最新的变化次数和一开始记录的变化次数是否相同,不相同就说明出现了并发修改,此时会报并发修改异常。
在这里插入图片描述
因此,无论我们使用迭代器、增强for还是lambda表达式(底层遍历方式都是迭代器),遍历集合的过程中,不要去使用集合中的方法去增加或删除元素。

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

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

相关文章

Android Studio安装教程

一.Android Studio安装教程 官网 Android Studio 下载最新版本的 Android Studio。官网地址&#xff1a;https://devel oper.android.google.cn/studio。

ChatGPT之搭建API代理服务

简介 一行Docker命令部署的 OpenAI/GPT API代理&#xff0c;支持SSE流式返回、腾讯云函数 。 项目地址&#xff1a;https://github.com/easychen/openai-api-proxy 这个项目可以自行搭建 OpenAI API 代理服务器工具&#xff0c;该项目是代理的服务器端&#xff0c;不是客户端。…

vuecli3 执行 npm run build 打包命令报错:TypeError: file.split is not a function

问题 今天有个项目在打包的时候遇到了一个问题&#xff0c;就是执行 npm run build 命令的时候报错了&#xff0c;如下&#xff1a; 解决 我排查了一下&#xff0c;模拟代码如下&#xff1a;在打包的时候用了 MinChunkSizePlugin const webpack require("webpack"…

【Web】CVE-2021-22448 Log4j RCE漏洞学习

目录 复现流程 漏洞原理 复现流程 启动HTTP->启动LDAP->执行Log4j vps起个http服务,放好Exploit.class这个恶意字节码 LDAPRefServer作为恶意LDAP服务器 import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import javax.ne…

java: 程序包sun.misc不存在

报错java: 程序包sun.misc不存在 启动失败&#xff0c;rebuild时也报错&#xff1a;java: 程序包sun.misc不存在 问题出在JDK版本上&#xff0c;这个包在JDK9的时候已经被弃用了&#xff0c;这里改回JDK8即可 解决办法步骤如下&#xff1a; 降JDK版本 切个1.8即可

大型软件编程实例分享,诊所门诊处方笺管理系统多台电脑同时使用的软件教程

大型软件编程实例分享&#xff0c;诊所门诊处方笺管理系统多台电脑同时使用的软件教程 一、前言 以下教程以 佳易王诊所门诊电子处方管理系统V17.2 为例说明 软件资源可以点击最下方官网卡片了解详情 软件左侧为导航栏 1、系统参数设置&#xff1a;可以设置打印等参数 2、…

后端登录校验

文章目录 登录校验CookieSessionJWT生成JWT校验JWT基于JWT进行身份验证CSRF Cookie、Session、Token的区别&#xff1f;过滤器(Filter)配置过滤器过滤器链 登录校验 由于HTTP协议是无状态的&#xff0c;我们在进行登录后等一系列接口请求是无法直接区分是哪一个用户的发给服务…

洗地机实用吗?旗舰洗地机品牌实测

在快节奏的生活中&#xff0c;拖地这项繁琐但又必不可少的家务总会在无形中增添我们的压力。喧嚣繁杂的城市生活、无尽的工作似乎已经占据了我们大部分的时间&#xff0c;回到家后还需要一把抹布一桶水开始无休止的的地板清洗&#xff0c;实在疲惫不堪。然而&#xff0c;科技的…

灵活应对:策略模式在软件设计中的应用

策略模式是一种行为型设计模式&#xff0c;它允许定义一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以互换使用。策略模式让算法的变化独立于使用算法的客户端&#xff0c;使得在不修改原有代码的情况下切换或扩展新的算法成为可能。 使用策略模式的场景包…

Vivado Tri-MAC IP的例化配置(三速以太网IP)

目录 1 Tri-MAC IP使用RGMII接口的例化配置1.1 Data Rate1.2 interface配置1.3 Shared Logic配置1.4 Features 2 配置完成IP例化视图 1 Tri-MAC IP使用RGMII接口的例化配置 在网络设计中&#xff0c;使用的IP核一般为三速以太网IP核&#xff0c;使用时在大多数场景下为配置为三…

MaxKey 单点登录认证系统——前端后端合并步骤

开发指南 | MaxKey单点登录认证系统 该项目前端是Angular项目&#xff0c;后端是springboot项目 以maxkey-web-app前后端合并为例 构建MaxKey统一认证前端 maxkey-web-frontend/maxkey-web-app ng build --prod --base-href /sign/static/以上sign由以下得来&#xff1a; 根…

Spark Shuffle Service简介与测试

一 Dynamic Resource Allocation(动态资源分配) 了解Shuffle Service之前&#xff0c;我们需要先了解和Shuffle Service有关的另一个特性&#xff1a;动态资源分配。 Spark管理资源有两种方式&#xff1a;静态资源分配和动态资源分配。 静态资源分配&#xff1a;spark提交任…