LinkedList简单介绍
这里是修真院后端小课堂,每篇分享文从 【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】 八个方面深度解析后端知识/技能,本篇分享的是: 【LinkedList简单介绍 】 大家好,我是IT修真院北京分院第27期的JAVA学员,一枚正直纯洁善良的java程序员。 今天给大家分享一下,修真院官网Java任务10,深度思考中的知识点———LinkedList简单介绍 1.背景介绍1.Collection 接口:?Collection接口是所有集合类的根节点,Collection表示一种规则,所有实现了Collection接口的类遵循这种规则 2.List 接口:?List是Collection的子接口,它是一个元素有序(按照插入的顺序维护元素顺序)、可重复、可以为null的集合 3.AbstractCollection 类:?Collection接口的骨架实现类,最小化实现了Collection接口所需要实现的工作量 4.AbstractList 类:?List接口的骨架实现类,最小化实现了List接口所需要实现的工作量 5.Cloneable 接口:?实现了该接口的类可以显示的调用Object.clone()方法,合法的对该类实例进行字段复制,如果没有实现Cloneable接口的实例上调用Obejct.clone()方法,会抛出CloneNotSupportException异常。正常情况下,实现了Cloneable接口的类会以公共方法重写Object.clone() 6.Serializable 接口:?实现了该接口标示了类可以被序列化和反序列化,具体的? 7.Deque 接口:?Deque定义了一个线性Collection,支持在两端插入和删除元素,Deque实际是“double ended queue(双端队列)”的简称,大多数Deque接口的实现都不会限制元素的数量,但是这个队列既支持有容量限制的实现,也支持没有容量限制的实现,比如LinkedList就是有容量限制的实现,其最大的容量为Integer.MAX_VALUE 8.AbstractSequentialList 类:?提供了List接口的骨干实现,最大限度地减少了实现受“连续访问”数据存储(如链表)支持的此接口所需的工作,对于随机访问数据(如数组),应该优先使用 AbstractList,而不是使用AbstractSequentailList类 ? 2.知识剖析构造方法
? ? //集合元素数量
? ? transient int size = 0;
? ? //链表头节点
? ? transient Node 节点Node结构:
? ? private static class Node ? ? ? ? Node(Node 增 1 addAll 接上文,先说addAll //addAll,在尾部批量增加 public boolean addAll(Collection extends E> c) { ? ? return addAll(size,c);//以size为插入下标,插入集合c中所有元素 } //以index为插入下标,插入集合c中所有元素 public boolean addAll(int index,Collection extends E> c) { ? ? checkPositionIndex(index);//检查越界 [0,size] 闭区间 ? ? Object[] a = c.toArray();//拿到目标集合数组 ? ? int numNew = a.length;//新增元素的数量 ? ? if (numNew == 0)//如果新增元素数量为0,则不增加,并返回false ? ? ? ? return false; ? ? Node ? ? if (succ == null) {//循环结束后,判断,如果后置节点是null。 说明此时是在队尾append的。 ? ? ? ? last = pred; //则设置尾节点 ? ? } else { ? ? ? ? pred.next = succ; // 否则是在队中插入的节点 ,更新前置节点 后置节点 ? ? ? ? succ.prev = pred; //更新后置节点的前置节点 ? ? } ? ? size += numNew; ?// 修改数量size
? ? modCount++; ?//修改modCount
? ? return true;
}
//根据index 查询出Node,
Node private void checkPositionIndex(int index) { ? ? if (!isPositionIndex(index)) ? ? ? ? throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isPositionIndex(int index) { ? ? return index >= 0 && index <= size; ?//插入时的检查,下标可以是size [0,size] } 小结: 链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。对比ArrayList是通过System.arraycopy完成批量增加的 通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率 2 插入单个节点add //在尾部插入一个节点: add public boolean add(E e) { ? ? linkLast(e); ? ? return true; } //生成新节点 并插入到 链表尾部, 更新 last/first 节点。
void linkLast(E e) {?
? ? final Node 删 remove
//删:remove目标节点
public E remove(int index) {
? ? checkElementIndex(index);//检查是否越界 下标[0,size)
? ? return unlink(node(index));//从链表上删除某节点
}
//从链表上删除x节点
E unlink(Node ? ? if (prev == null) { //如果前置节点为空(说明当前节点原本是头结点) ? ? ? ? first = next; ?//则头结点等于后置节点? ? ? } else {? ? ? ? ? prev.next = next; ? ? ? ? x.prev = null; //将当前节点的 前置节点置空 ? ? } ? ? if (next == null) {//如果后置节点为空(说明当前节点原本是尾节点) ? ? ? ? last = prev; //则 尾节点为前置节点 ? ? } else { ? ? ? ? next.prev = prev; ? ? ? ? x.next = null;//将当前节点的 后置节点置空 ? ? } ? ? x.item = null; //将当前元素值置空 ? ? size--; //修改数量 ? ? modCount++; ?//修改modCount ? ? return element; //返回取出的元素值 } ? ? private void checkElementIndex(int index) { ? ? ? ? if (!isElementIndex(index)) ? ? ? ? ? ? throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ? ? } ? ? //下标[0,size) ? ? private boolean isElementIndex(int index) { ? ? ? ? return index >= 0 && index < size; ? ? } 删除链表中的指定节点: ? ? //因为要考虑 null元素,也是分情况遍历
? ? public boolean remove(Object o) {
? ? ? ? if (o == null) {//如果要删除的是null节点(从remove和add 里 可以看出,允许元素为null)
? ? ? ? ? ? //遍历每个节点 对比
? ? ? ? ? ? for (Node ? ? ? ? if (prev == null) {//前置节点为null, ? ? ? ? ? ? first = next;//则首节点为next ? ? ? ? } else {//否则 更新前置节点的后置节点 ? ? ? ? ? ? prev.next = next; ? ? ? ? ? ? x.prev = null;//记得将要删除节点的前置节点置null ? ? ? ? } ? ? ? ? //如果后置节点为null,说明是尾节点 ? ? ? ? if (next == null) { ? ? ? ? ? ? last = prev; ? ? ? ? } else {//否则更新 后置节点的前置节点 ? ? ? ? ? ? next.prev = prev; ? ? ? ? ? ? x.next = null;//记得删除节点的后置节点为null ? ? ? ? } ? ? ? ? //将删除节点的元素值置null,以便GC ? ? ? ? x.item = null; ? ? ? ? size--;//修改size ? ? ? ? modCount++;//修改modCount ? ? ? ? return element;//返回删除的元素值 ? ? } 删也一定会修改modCount。 按下标删,也是先根据index找到Node,然后去链表上unlink掉这个Node。 按元素删,会先去遍历链表寻找是否有该Node,考虑到允许null值,所以会遍历两遍,然后再去unlink它。 改set
public E set(int index,E element) {
? ? checkElementIndex(index); //检查越界[0,size)
? ? Node 查get //根据index查询节点 public E get(int index) { ? ? checkElementIndex(index);//判断是否越界 [0,size) ? ? return node(index).item; //调用node()方法 取出 Node节点, } //根据节点对象,查询下标 ? ? public int indexOf(Object o) {
? ? ? ? int index = 0;
? ? ? ? if (o == null) {//如果目标对象是null
? ? ? ? //遍历链表
? ? ? ? ? ? for (Node ? ? public int lastIndexOf(Object o) {
? ? ? ? int index = size;
? ? ? ? if (o == null) {
? ? ? ? ? ? for (Node toArray() 我们也顺带看一下toArray().毕竟这是个高频的API ? ? public Object[] toArray() {
? ? ? ? //new 一个新数组 然后遍历链表,将每个元素存在数组里,返回
? ? ? ? Object[] result = new Object[size];
? ? ? ? int i = 0;
? ? ? ? for (Node 3.常见问题总结一下LinkedList ? 4.解决方案LinkedList 是双向链表。 链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。对比ArrayList是通过System.arraycopy完成批量增加的。增加一定会修改modCount。 通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率 删也一定会修改modCount。 按下标删,也是先根据index找到Node,然后去链表上unlink掉这个Node。 按元素删,会先去遍历链表寻找是否有该Node,如果有,去链表上unlink掉这个Node。 改也是先根据index找到Node,然后替换值。改不修改modCount。 查本身就是根据index找到Node。 所以它的CRUD操作里,都涉及到根据index去找到Node的操作。 5.编码实战详见视频 6.扩展思考
7.????参考文献List集合就这么简单【源码剖析】: https://segmentfault.com/a/1190000014240704 8.????更多讨论Q1:Deque接口是什么,定义了一个怎样的规范? A1:Deque?表示双端队列。 双端队列是在两端都可以进行插入和删除的队列。?Deque 是一个比 Stack 和 Queue 功能更强大的接口,它同时实现了栈和队列的功能。 ArrayDeque 和 LinkeList 实现了?Deque 接口 Q2:LinkedList是双向链表,其底层实现是怎样的,具体包含哪些操作? A2:在总结部分可以看出 Q3:ArrayList和LinkedList的特点和区别? A3: 数组和链表的特性差异,本质是:连续空间存储和非连续空间存储的差异,主要有下面两点:
今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~ PPT链接?视频链接 更多内容,可以加入IT交流群565734203与大家一起讨论交流 这里是技能树·IT修真院:,初学者转行到互联网的聚集地 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |