java集合【10】——— LinkedList源码解析
1.LinkedList介绍
我们除了最最常用的ArrayList
之外,还有LinkedList
,这到底是什么东西?从LinkedList官方文档,我们可以了解到,它其实是实现了List
和Queue
的双向链表结构,而ArrayList
底层则是数组结构。
下面的讲解基于jdk 1.8
:
继承了AbstractSequentialList
,实现了List
,Queue
,Cloneable
,Serializable
,既可以当成列表使用,也可以当成队列,堆栈使用。主要特点有:
- 线程不安全,不同步,如果需要同步需要使用
List list = Collections.synchronizedList(new LinkedList());
- 实现
List
接口,可以对它进行队列操作 - 实现
Queue
接口,可以当成堆栈或者双向队列使用 - 实现Cloneable接口,可以被克隆,浅拷贝
- 实现
Serializable
,可以被序列化和反序列化
下面是LinkedList
的结构,注意:指针结束指向的是node,开始的是prev
或者next
源码定义如下:
2.成员变量
成员变量相对比较简单,因为不像ArrayList
一样,需要使用数组保存元素,LinkedList
是靠引用来关联前后节点,所以这里只有大小,第一个节点,最后一个节点,以及序列化的uid。
我们来看看Node
到底是何方神圣?
其实就是内部类,里面的item
是真正保存节点的地方,next是下一个节点的引用,prev
是上一个节点的引用。这里也体现了LinkedList
其实就是双线链表。
只有一个构造函数,三个参数分别对应三个属性。
3. 构造函数
构造函数有两个,一个是无参数构造函数,另一个是初始化集合元素,里面调用的其实是addAll
,一看就是将里面所有的元素加入到集合中。
4. 常用List方法解析
4.1 查找相关
4.1.1 getFirst()
获取第一个元素:
4.1.2 getLast()
获取最后一个元素,和获取第一个的原理差不多
4.1.3 get(int index)
通过索引来获取元素,里面是调用了另外一个方法先获取节点,再获取该节点的item
,在此之前,做了index
安全性校验。
在👆上面的代码中调用了通过索引位置查找节点位置的函数,下面我们来分析一下这个函数,由于底层是链表实现的,所以呢?遍历起来不是很方便,就考虑到位运算,如果索引位置在后面一半,就从后往前遍历查找,否则从前往后遍历。
4.1.4 indexOf(Object o)
查找某一个元素的索引位置,分为两种情况讨论,如果要查找的元素为空,那么就使用==
,否则使用equals()
,这也从侧面印证了LinedList
实际上是可以存储null
元素的。使用计数查找:
4.1.5 lastIndexOf(Object o)
和前面的indexOf
差不多,区别就是这个是后面开始查找,找到第一个符合的元素。
4.2 添加元素
4.2.1 addFirst(E e)
将元素e,添加到第一个节点,公有方法是addFirst()
,但是其实内部调用是linkFirst()
,这是private
方法。
4.2.2 addLast(E e)
将元素添加在链表最后,其实内部也是直接调用的private
方法linkLast()
:
4.2.3 add(E e)
增加元素,默认也是在链表的最后添加,完成返回true:
4.2.4 addAll(Collection<? extends E> c)
往链表里面批量添加元素,里面默认是在最后面批量添加,内部调用的是addAll(int index, Collection<? extends E> c)
,添加之前会判断索引位置是不是合法的。
然后查找需要插入的位置的前后节点,循环插入。
上面的代码调用了node(index)
,这个在前面查找的时候已经说过了,不再解释。
4.2.5 addAll(int index, Collection<? extends E> c)
在指定位置批量插入节点:
4.2.6 add(int index,E element)
将元素插入在指定位置,先判断索引位置,如果索引位置是最后一个,那么直接调用在最后添加元素函数即可,否则需要调用另外一个函数,在某个元素前面插入:
4.3 删除元素
4.3.1 removeFirst()
删除第一个节点,先获取首节点,判断第一个节点是不是为空,如果为空则证明没有该节点,抛出异常,内部调用的其实是unlinkFirst()
。返回值是被移除的节点里面的数值。
4.3.2 removeLast()
删除最后一个节点,和上面的删除首节点差不多,先取出最后一个节点,判断是否为空,如果为空则抛出异常,否则会调用另一个解除连接的函数unLinkLast()
。
4.3.3 remove(Object o)
删除某个元素分为两种情况,元素为null和非null,直接遍历判断,里面真正删除的方法其实是unlink(E e)
,成功移除则返回true,注意这里只会移除掉第一个,后续要是还有该节点,不会移除。
unLink(E e)
方法如下:
4.3.4 clear()
移除里面所有的元素:
4.3.5 remove(int index)
移除指定索引的元素。先通过索引找到节点,再移除指定的节点
4.4 更新元素
4.4.1 set(int index,E element)
更新指定索引的位置的元素,首先通过索引查找到该元素,然后修改item值,返回旧的item值。
5 queue相关的方法
因为LinkedList
也实现了queue
接口,所以它肯定也实现了相关的方法,下面我们看看:
5.1 peek()
获取队列第一个元素:
5.2 element()
也是获取队列第一个元素,里面调用的是getFirst()
。
5.3 poll()
移除队列第一个节点元素并返回,里面调用的其实是unlinkFirst()
5.4 remove()
移除队列第一个元素,里面调用的是removeFirst()
:
5.5 offfer(E e)
在队列后面增加元素:
5.6 offerFirst(E e)
往队列的前面插入元素,其实调用的是addFirst()
5.7 offerLast(E e)
往队列的后面添加元素,其实调用的是addList()
5.8 peekFirst()
获取第一个节点里面的元素:
5.9 peekLast()
获取最后一个节点的元素:
5.10 pollFirst()
获取第一个元素,并且移除它,使用的是unlinkFirst(E e)
5.11 pollLast()
获取队列最后一个元素,并且移除它,调用的其实是unlinkLast(E e)
5.12 push(E e)
像是堆栈的特点,在前面添加元素:
5.13 pop()
堆栈的特点,取出队列首的第一个元素
5.14 removeFirstOccurrence(Object o)
移除元素,从前往后第一次出现的地方移除掉:
5.15 removeLastOccurrence(Object o)
移除元素,最后一次出现的地方移除掉,和前面分析的一样,分为两种情况,null和非null。
6.其他方法
是否包含某个元素,其实调用的是indexOf()
方法,如果返回的索引不为-1,则包含:
返回大小:
是否为有效元素下标索引,从0到size-1
是否为有效位置索引,从0到size
获取指定索引位置的ListIterator
:
获取倒序的迭代器:
拷贝克隆函数,一个是父类的克隆函数,另一个是重写的克隆函数,这里比较特殊,因为LinkedList
是链表,本身只保存了第一个和最后一个的引用,所以拷贝的时候需要向里面添加元素的方式进行拷贝。
转换成为数组,通过循环实现
转换成为指定类型的数组,和前面不同的是,这里初始化的时候使用类型反射创建(T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size)
获取可分割迭代器:
7.迭代器
里面定义了三种迭代器,都是以内部类的方式实现,分别是:
- ListItr:列表的经典迭代器
- DescendingIterator:倒序迭代器
- LLSpliterator:可分割迭代器
7.1 ListItr
先来说说ListItr
,这个迭代器主要是有next()
,hashNext()
,hasPrevious()
,previous()
,nextIndex()
,previousIndex()
,remove()
,set()
,add()
,forEachRemaining()
方法:
next()
:获取下一个元素hashNext()
:是否有下一个元素hasPrevious()
:是否有上一个元素previous()
:上一个元素nextIndex()
:下一个索引位置previousIndex()
:上一个索引位置remove()
:删除当前索引位置的元素set()
:更新元素add()
:新增元素forEachRemaining()
:遍历剩下的元素
里面主要有集合重要的属性:
lastReturned
:上一次返回的元素next
:下一个返回的元素nextIndex
:下一个索引expectedModCount
:期待修改的次数
上面的迭代器没有什么好说的,就是往前面和后面遍历的功能,以及增删改的功能。
7.2 DescendingIterator
这个迭代器有点意思,也很简单,就是一个倒序的功能,功能实现也十分简单:
- hasNext:是否有下一个元素,实际上是判断上一个元素
- next:获取下一个元素,实际上是获取前面一个元素
- remove:移除元素
倒序就是别人从前往后,它偏偏从后往前遍历,emmmmmmm
7.3 LLSpliterator
这个迭代器有点东西,感觉和其它的不太一样,LLSpliterator
是在使用node的next进行迭代,下面分析一下:主要是为了将元素分为多份,然后再用多线程来处理。
值得注意的是:分割的时候,LinkedList
不是1/2分割,而是每一次分割出来的大小都是递增的,递增的大小是BATCH_UNIT
,但是返回的不是LLSpliterator
,而是ArraySpliterator
,每次都分割出更多的元素,转成数组结构,这也许是出自于性能考虑,比较指针遍历太慢了,我猜的的...别打我
8.序列化和反序列化
序列化和反序列化的时候,需要重写,因为我们保存的只有第一个和最后一个节点的引用,我们序列化需要保存大小和引用,所以需要重写,要不反序列化回来就找不到next
,节点之间的关系就会丢失。
序列化的时候如下,写入了size
,以及遍历的时候将节点的item
值写入。
反序列化的时候,读入大小size
以及每个节点里面的元素item
9.总结一下
-
LinkedList
底层是用链表实现的,而且是双向链表,并且实现了Queue
接口,可以当成双向队列或者堆栈来使用。也正是因为是链表实现,所以删除元素比较快,但是查找的时候相对较慢。当然,也没有什么扩容,除非就是内存不够了。 -
双向链表,可以从头往尾遍历,也可以从尾部往前遍历。
-
LinkedList
继承了AbstractSequentialList
,AbstractSequentialList
实现了get
,set
,add
,remove
等方法。 -
序列化/反序列化的时候重写了方法,才能达到序列化里面每一个节点元素的效果。
-
线程不安全
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。
此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~
__EOF__

本文链接:https://www.cnblogs.com/Damaer/p/14091595.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 打造的强大开源交互式图表库