Java集合【8】-- ArrayList源码分析
1. ArrayList
ArrayList
是最最常用的集合类了,真的没有之一。下面的分析是基于1.8.0_261源码进行分析的。
1.1 ArrayList特点介绍
动态数组,使用的时候,只需要操作即可,内部已经实现扩容机制。
- 线程不安全
- 有顺序,会按照添加进去的顺序排好
- 基于数组实现,随机访问速度快,插入和删除较慢一点
- 可以插入null元素,且可以重复
1.2 实现的接口和继承的类
ArrayList
继承了AbstractList
接口,实现了List
,以及随机访问,可克隆,序列化接口。不是线程安全的,如果需要线程安全,则需要选择其他的类或者使用Collections.synchronizedList(arrayList)
允许存储null元素,也允许相同的元素存在。
其底层实际上是数组实现的,那为什么我们使用的时候只管往里面存东西,不用关心其大小呢?因为ArrayList
封装了这样的功能,容量可以动态按需变化,不需要使用者关心。扩容之后不会自动缩小容量。
2. 成员变量
elementData
是真正存储数据元素的地方,transient
表示这个属性不需要自动序列化。
关于上面的transient,找到一个靠谱的说法:
在ArrayList中的elementData这个数组的长度是变长的,java在扩容的时候,有一个扩容因子,也就是说这个数组的长度是大于等于ArrayList的长度的,我们不希望在序列化的时候将其中的空元素也序列化到磁盘中去,所以需要手动的序列化数组对象,所以使用了transient来禁止自动序列化这个数组。
那在哪里去实现对西那个的序列化和反序列化的呢?
这个需要我们看源码里面的readOject()
和writeOject()
两个方法。其实就除了默认的序列化其他字段,这个elementData
字段,还需要手动序列化和反序列化。
很多人可能会有疑问,为什么这个函数没有看到有调用呢?在哪里调用的呢?
其实就是在对象流中,通过反射的方式进行调用的,如果有自己的实现,就会调用到这里的实现,这里就不展开了,下次一定!!!有兴趣可以看看。
如果我们创建的时候不指定大小,那么就会初始化一个默认大小为10,DEFAULT_CAPACITY
就是默认大小。
里面定义了两个空数组,EMPTY_ELEMENTDATA
名为空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
名为默认大小空数组,用来区分是空构造函数还是带参数构造函数构造的ArrayList
,第一次添加元素的时候使用不同的扩容方式。
之所以是一个空数组,不是null,是因为使用的时候我们需要制定参数的类型,如果是null,那就根本不知道元素类型是什么了。
还有一个特殊的成员变量modCount
,这是快速失败机制所需要的,也就是记录修改操作的次数,主要是迭代遍历的时候,防止元素被修改。如果操作前后的修改次数对不上,那么有些操作就是非法的。transient
表示这个属性不需要自动序列化。
序列化idserialVersionUID
如下:
为什么需要这个字段呢?这是因为如果没有显示声明这个字段,那么序列化的时候回自动生成一个序列化的id,写到序列化文件中去,这样子的话,假设序列化完成之后,往原来的类里面添加了一个字段,那么这个时候反序列化会失败,因为默认的序列化id已经改变了。假设我们给它指定了序列化id的话,就可以避免这种问题,只是增加的字段反序列化的时候是空的。
3. 构造方法
构造方法有三个,可以指定容量,可以指定初始的元素集合,也可以什么都不指定。
4. 常用增删改查方法
添加元素
add()
方法有两个:
add(E e)
:添加一个元素,默认是在末尾添加add(int index, E element)
:在指定位置index添加(插入)一个元素
查询元素
get()
方法相对比较简单,获取之前检查参数是否合法,就可以返回元素了。
更新元素
set()
和之前的get()
有点像,但是必须制定修改的元素下标,检查下标之后,修改,然后返回旧的值。
删除元素
按照元素移除,或者按照下标移除元素
5.自动扩容和手动缩容机制
5.1 自动扩容
ArrayList
是基于数组动态扩容的,那它什么时候扩容的呢?好像上面的源代码中我们没有看到,其实是有的,所谓扩容嘛,就是容量不够了,那么容量不够的时候只会发生在初始化一个集合的时候或者是增加元素的时候,所以是在add()
方法里面去调用的。
在最小调用的时候容量不满足的时候,会调用grow()
,grow()
是真正扩容的函数,这里不展开了。
如果元素是默认初始haul的空数据,那么所需要的最小容量就是默认容量和最小容量对比,两者取最大,也就是突然有加入有6个元素加到集合中来,那么默认容量是10,会直接初始化为10,如果一下子有11个元素加进来,那么最小的容量应该取11。
真正实现扩容的代码如下:
- 如果所需最小容量小于0,抛出异常
- 如果所需最小容量大于最大的容量,那么直接返回int类型的最大值作为容量
- 如果所需的最小容量小于等于最大容量,那么直接返回最大的容量。
如果需要手动扩容,其实也是有提供函数的,其参数是所需要的最小的容量,内部调用也是上面说的ensureExplicitCapacity
函数。
5.2 手动扩容
手动缩容的函数相对简单,修改次数增加,然后,将数组元素copy到新的数组中即可。
6. 其他函数
获取大小:
判断是不是空集合:
是否包含某个对象:
返回对象的下标,从左到右遍历,分为object为null和非null来处理:
放回对象最后出现的下标,从右往左遍历:
克隆ArrayList对象,先拷贝ArrayList,然后再把内部的数组也拷贝一份:
转化为数组,我们看到内部其实是复制了一份引用,所以如果我们修改数组里面的元素,也会修改到ArrayList
元素的。
将元素拷贝到指定的数组中:
根据index索引位置获取迭代器位置:
截取一段,返回新的list,返回的是一个内部类SubList
对象。
遍历方法,其实就是lambda的方式进行调用,将行为参数化,将行为传进去,处理。
根据条件移除元素,也是lambda表达式:
排序,lambda表达式:
7. 迭代器
源码中一共定义了三个迭代器:
Itr
:实现了Iterator
接口,是AbstractList.Itr
的优化版本。ListItr
:继承了Itr
,实现了ListIterator
,是AbstractList.ListItr
优化版本。ArrayListSpliterator
:继承于Spliterator
,Java 8 新增的迭代器,基于索引,二分的,懒加载器。
7.1 Itr
Itr
这是一个比较初级的迭代器,实现了Iterator
接口,有判断是否有下一个元素,访问下一个元素,删除元素的方法以及遍历对每一个元素处理的方法。
里面有两个比较重要的属性:
- cursor:下一个即将访问的元素下标
- lastRet:上一个返回的元素下标,初始化为-1
两个重要的方法:
- next():获取下一个元素
- remove():移除当前元素,需要在next()方法调用之后,才能调用,要不会报错。
7.2 ListItr
继承了Itr
,实现了ListIterator
,主要增加的功能有:
- 根据index获取该位置的迭代器
- 判断是否有前面的元素
- 获取下一个返回元素的下标
- 获取上一个返回元素的下面
- 获取上一个元素
- 更新元素
- 增加元素
总结来说,就是在Itr
的基础上,增加了更多的功能。
7.3 ArrayListSpliterator
直接看源码,这是一个用来适应多线程并行迭代的迭代器,可以将集合分成多端,进行处理,每一个线程执行一段,那么就不会相互干扰,它可以做到线程安全。
以上三种迭代器,各有千秋,Itr
功能比较简单,提供了常用的功能,ListItr
,提供了双向遍历和更新,插入等操作,而ArrayListSpliterator
则是专注于切分迭代。使用的时候按需使用即可。
8. 小结一下
- ArrayList是基于动态数组实现的,增加元素的时候,可能会触发扩容操作。扩容之后会触发数组的拷贝复制。remove操作也会触发复制,后面的元素统一往前面挪一位,原先最后面的元素会置空,这样可以方便垃圾回收。
- 默认的初始化容量是10,容量不够的时候,扩容时候增加为原先容量的一般,也就是原来的1.5倍。
- 线程不安全,但是元素可以重复,而且可以放null值,这个需要注意一下,每次取出来的时候是需要判断是不是为空。
- 查询效率比较高,因为底层是数组,但是插入和删除操作,成本相对高一点。
此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使缓慢,驰而不息。
公众号:秦怀杂货店
__EOF__

本文链接:https://www.cnblogs.com/Damaer/p/13992159.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库