LinkedList源码学习
时间:2022-07-24
本文章向大家介绍LinkedList源码学习,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
之前学习了ArrayList,了解了其基于数组的本质,那么LinkedList是怎么实现的?显然LinkedList是链表。也就是基于链表实现。链表分为单向链表和多向链表。那么LinnkedList具体是那种类型的链表?我们可能在工作中一直在用但是也许对LinkedList的原理不熟悉。怀着疑问,我们来解析一下吧!
在类的继承关系图中我们看到了Queue的接口,这个接口是操作队列的一些公用的接口。那么放着这里又是为了什么呐?显然LinkedList的基础操作具有和队列相似的地方。总体来说就是说LinkedList具有list和队列的双重属性。
//链表的长度
transient int size = 0;
//头节点地址
transient Node<E> first;
//尾节点地址
transient Node<E> last;
LinkedList的限制条件和容器就这三个,而却有两个,那么这是为何?因为在我们的固有思维里一个链表应该就可以搞定了。为啥这里要定义两个。是因为要兼容队列的原因吗?
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//双向链表
private static class Node<E> {
E item;
//下一节点
Node<E> next;
//前一节点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//将元素添加到链表中
public boolean addAll(int index, Collection<? extends E> c) {
//检测下标
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
//排除空列表
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
//如果是添加到最后
succ = null;
//缓存last指针
pred = last;
} else {
//向指定位置添加元素
succ = node(index);
//缓存添加位置的前一节点
pred = succ.prev;
}
for (Object o : a) {
//声明一个前一节点为last的新节点
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
//让原来的last指向新节点
pred.next = newNode;
//last指针向后移动
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
//将last指针的末尾添加succ
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//向头节点添加元素
private void linkFirst(E e) {
//缓存头节点的指针
final Node<E> f = first;
//声明一个next是头节点的节点
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//将新节点添加到链表额头部
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
//向尾节点之后添加新节点
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//添加元素
public boolean add(E e) {
//向尾节点之后添加
linkLast(e);
return true;
}
//向指定下标添加
public void add(int index, E element) {
//保障添加额元素的位置不是非法值,小于0或大于size
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
//在指定元素之前添加元素
linkBefore(element, node(index));
}
//在指定元素之前添加
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//缓存指定元素的头节点
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
在移除元素时
//移除指定元素
public E remove(int index) {
//下标检测
checkElementIndex(index);
//移除指定元素
return unlink(node(index));
}
//移除
E unlink(Node<E> x) {
final E element = x.item;
//缓存当前节点额前一节点和后一节点
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//如果前一节点为null,说明就是头节点了
if (prev == null) {
first = next;
} else {
//跨过移除的节点
prev.next = next;
//标记一下,然后好让GC回收
x.prev = null;
}
//如果next是null,说明是尾节点
if (next == null) {
last = prev;
} else {
//绕过移除的节点
next.prev = prev;
x.next = null;
}
//帮助gc
x.item = null;
//让size--
size--;
modCount++;
return element;
}
总结:通过上述分析,发现LinkedList也没有什么神秘的地方。很简单的类,但是因为LindedList是链表。所以内存的使用概率是非常好的,但是对元素的寻址是需要进行计算的,通过移动指针去寻找,和数组的统一地址偏移量的所以还是有些差距。显然是数组的寻址快一点。也就是说LinkedList的get和set比较消耗时间。但是LinkedList在删除元素只需要修改相关的前后两个指针,所以效率非常好,而ArrayList则需要进行元素值得覆盖和移动。所以如果一个列表元素增删比较频繁的话就可以采用LinkedList,如果对数据的读操作比较频繁的话可以采用ArrayList。
- Python原创0基础入门一看几张图就学会了
- ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
- 了解ASP.NET MVC几种ActionResult的本质:FileResult
- ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求
- 如何用Python和深度神经网络识别图像?
- 余军:分布式数据库在金融行业的创新实践
- 微信小游戏采用了我们都忽略的产品推广新切入点
- ASP.NET MVC下的四种验证编程方式[续篇]
- 如何把业务问题变成机器学习的问题?
- 这算是ASP.NET MVC的一个大BUG吗?
- 【Scikit-Learn 中文文档】分解成分中的信号(矩阵分解问题) - 无监督学习 - 用户指南 | ApacheCN
- 区块链技术在非能源领域的应用场景
- Python读书笔记8
- How to debug .NET Core RC2 app with Visual Studio Code on Windows?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法