用pv操作描述如下前驱图_LinkedList实现分析(二)——常用操作

上一篇文章LinkedList实现分析(一)——LinkedList初探与对象创建介绍了LinkedList中的一些重要属性和构造方法,下面我们将详细介绍一下LinkedList提高的常用方法的实现原理

元素添加

###add(E e)方法

往LinkedList添加元素,LinkedList提供了多重方式,首先介绍add方法,下面我们使用一个简单的实例代码来分析一下add方法的实现原理:

List myList = new LinkedList();//1myList.add("a"); //2myList.add("b"); //3myList.add("c"); //4

执行第一行代码后创建了一个空的myList对象,此时myList如下图所示:

e78a054de569d66e929cd4b7782d35f1.png

myList

然后执行第2行代码myList.add("a"),把字符串"a"添加到myList中,通过debug方式进入add方法内部:

public boolean add(E e) { linkLast(e); return true;}

add又调用linkLast方法,linkLast方法实现如下:

void linkLast(E e) { final Node l = last; final Node newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;}

在linkLast内部,此时e="a"。首先把last,此时myList是新创建的对象,last=null,赋值给一个临时变量 l,这里的做法是用来保存last的值,然后用元素e创建一个新的Node对象,这里是往myList的尾部添加元素,因此创建的Node节点的构造方法最后一个参数是null,而第一个参数表示的是插入元素e的前一个元素指针,因此需要传入l,因为在整个LinkedList中last属性表示的最后一个元素。当创建好包含e的newNode节点的时候,此时newNode就应该是myList的最后一个节点,也是第一个节点(l==null),因此把newNode赋值给last和first,然后把表示myList大学的size进行加一,最后对modCount加一,记录了修改次数。执行完第2行代码后,myList的状态入下图所示:

298a7f22d5eb958097e5d8ee05041fc0.png

myList

当示例代码执行第3行代码的时,同样会执行linkLast,此时e=“b”。在进入linkLast方法的时候,last的值已经不在是null,而是刚刚创建的包含了“a”的Node节点对象,同样把last先用一个临时变量l保存起来,然后创建一个包含“b”的Node节点,这个时候该node节点的前一个节点是“a”所在的节点,因此构造函数 new Node<>(l, e, null),把新创建的newNode作为整个myList的最后一个节点: last = newNode,而由于l不为null,也就是last不为null,所以需要把新创建的节点放到myList中最后一个节点的后面: l.next = newNode,最后修改size和modCount的值,完成“b”元素的添加。此时myList的状态如下图所示:

f8b357e119219b6d1b574ab614c6bfdb.png

然后示例代码执行到第4行,此时myList已经有2个node,根据上图可知last指向了“b”node,然创建“c”的node对象,让“c”的prev执行“b”,然后myList中的last指向“c”,“b”的next执行“c”,执行完后myList的状态如下图所示:

eae581396c74dfa33b1f882ae29cfe30.png

### add(int index, E element)

该方法是在指定的索引的位置插入一个元素,index值指定的索引位置,element是需要插入的元素信息。这里还是以一个示例为基础进行对 add(int index, E element) 源码的分析,示例代码如下:

List myList = new LinkedList();//1myList.add("a"); //2myList.add(0,"b")//3

当示例代码执行完第2部的时候,myList的状态如下图所示:

248ab59935db696c1ac2ce5ef65b11c3.png

然后执行第3行代码,具体 add(int index, E element) 的源码如下:

public void add(int index, E element) { checkPositionIndex(index);//index校验 if (index == size) linkLast(element); else linkBefore(element, node(index));}

首先是对传入的index进行位置校验,具体实现是要求index不能小于0,不能大于myList当前的大小。根据示例代码,执行到第3行的时候,此时size=1,index=0,因此程序执行linkBefore方法,linkBefore是第一参数是新添加的元素element,第二个参数表示element需要被添加到myList中哪个元素的后面。这里传入是位置索引,因此需要调用node(index)方法找到mylist中index对应的node对象,node方法实现如下:

Node node(int index) { if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x;}

这里源码中使用了一个技巧,通过判断传入的index与整个list的大小(size)的1/2进行比较,如果index=size/2,那么对myList进行倒序遍历。这里需要大家注意的是源码中使用右移操作获取size的1/2,这个技巧希望大家可以掌握:

>在java中,数字左移表示乘2,数字右移除2

由于当前myList的size=1,右移之后是0,index=0,因此执行else的语句,从last指向的node开始倒序遍历,此时myList只有一个index=0所指向的元素就是last的值,因此把last返回。node方法执行完之后,开始执行linkBefore方法:

void linkBefore(E e, Node succ) { final Node pred = succ.prev; final Node newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++;}

linkBefore方法就是一个双向列表的插入操作,首先把succ的前驱节点prev保存到临时变量pred,使用传入的e(根据示例代码,此时e=“b”)创建一个新的Node对象:newNode,由于是在succ节点的前面插入新元素"b",因此newNode的前驱点是之前succ节点的前驱节点,newNode的后节点就是succ节点本身:final Node newNode = new Node<>(pred, e, succ),此时myList的状态如下图所示:

0a61e693f3661dfa5992bc22cc324a78.png

然后执行 succ.prev = newNode,是把原来succ指向的前驱节点改成了newNode,因为newNode插入了succ节点前面,因此succ的prev就是newNode,指向完的myList状态如下:

54ba3a3ad9d1758d0ecd6f050062f4d7.png

然后是判断pred是否为null,在myList的当前状态中, succ.prev是null,pred也就是null,所以把first指向newNode,也就是在myList的头结点succ之前添加一个新元素newNode,即 first = newNode;然后对size和modCount加一,执行完最后myList的状态如下图:

3753c31e41e8bd3213673ad551abd02f.png

### addAll(Collection extends E> c)

下面介绍一下使用集合对象给LinkedList添加元素,addAll有两个重载方法:

public boolean addAll(Collection extends E> c)public boolean addAll(int index, Collection extends E> c)

在源码中,addAll(Collection extends E> c)实际是调用了addAll(int index, Collection extends E> c) :

public boolean addAll(Collection extends E> c) { return addAll(size, c);}

因此这里只介绍后面一个addAll的实现:

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 pred, succ; if (index == size) { succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } //把a中的元素创建为一个列表 //并且让pred指向a的最后一个节点 for (Object o : a) { Node newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } //如果没有后继节点,那么c集合中最后一个节点就是 //整个list的最后节点 if (succ == null) { last = pred; } else { //如果在list找到插入的前驱和后继节点 //把c中的最后一个节点的后继节点指向succ //把后继节点的前驱节点指向c中的最后一个节点 pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true;}

addAll方法内部,首先还是判断index的值是否合法,然后对传入的Collection对象c进行验证, 接着在源码中定义了两个变量:

Node pred, succ;

pred表示需要插入c的前驱节点,succ表示c的后继节点。当index=size表示是把c添加到LinkedList的尾部,因此执行succ=null和pred=last;否则调用 node(index)方法,找到后继节点succ,然后把当前后继节点的前驱节点赋值给pred。

找到所插入前驱和后继节点之后,开始循环遍历c,把c中的每一个元素创建一个Node对象,c中的前一个元素都是后一个元素的前驱节点,把c建立为一个链表。然后把c的链表插入的LinkedList中,具体见上面源码的注释,完成链表的连接操作只会,对size和modCount进行加1操作。

### 其他添加元素的方法

下面介绍一个往list头插入元素的方法:

public void addFirst(E e) {linkFirst(e);}private void linkFirst(E e) { //保存第一个元素 final Node f = first; //头插法,因此newNode的前驱节点是null,后继节点是f final Node newNode = new Node<>(null, e, f); //让新创建节点当整个list的头节点 first = newNode; if (f == null) //如果f=null,表示当前list没有一个元素,因此需要给last=newNode last = newNode; else //把之前的first接的前驱节点换成新创建的newNode f.prev = newNode; size++; modCount++; }

头插入的原理就是在整个list中,找到一个元素first,把first的前驱节点换成新插入的元素,然后把新传入元素的后继节点指向之前的头节点,具体实现见上面源码和注释

LinkedList提供了offer(E e),offerFirst(E e), offerLast(E e),addFirst(E e) ,addLast(E e),push(E e) 往list中添加一个元素的方法,这些方法的底层都是基于上面介绍的原理实现的:头插和尾插。这里就不多做说明。

获取元素

LinkedList获取元素提供多种方法:

//根据索引获取元素public E get(int index)//获取头元素public E peek()//获取头元素,并且把头元素从list中删除public E poll()//获取头元素,并且把头元素从list中删除public E pop()```下面追个介绍这些方法的具体实现:```public E get(int index) {checkElementIndex(index);return node(index).item;}

由上面源码可知,get操作底层是调用的node方法,node方法参考上面的介绍。

下面是peek,peek操作是获取头元素,因此只需要获取到LinkedList的first所执行的元素就可以了。

public E peek() {final Node f = first;return (f == null) ? null : f.item;}

下面介绍poll():

public E poll() { final Node f = first; return (f == null) ? null : unlinkFirst(f);}private E unlinkFirst(Node f) { //保存f的具体值 final E element = f.item; final Node next = f.next; f.item = null; f.next = null; // help GC //f的后继节点赋值给LinkedList的first first = next; //next==null表示要删除的节点是list的最后一个节点 if (next == null) last = null; else //表示删除f之后的list列表中,头结点的prev应该为null,而之前 //prev是执行f的 next.prev = null; size--; modCount++; return element; }

由上面源码可知,当调用poll的时候,会把LinkedList的头元素返回,然后把头元素从LinkedList中删除。

下面看一下pop方法:

public E removeFirst() {final Node f = first;if (f == null) throw new NoSuchElementException();return unlinkFirst(f);}

pop方法的底层也是调用unlinkFirst方法完成头结点的删除操作。

##To Array的操作

LinkedList提供了两种方法完成list到数组的转换:

public Object[] toArray()public  T[] toArray(T[] a)

第一个方法,是把list转为Object类型的数组,具体是实现如下:

public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Node x = first; x != null; x = x.next) result[i++] = x.item; return result; }

该方法的实现原理比较简单,首先创建一个大小与list相同的object类型数组,然后从头到尾遍历list的元素,把每个元素放到创建的数组中,因为数组对象是Object类型,因此list中的任何元素都可以直接放入到数组中,最后把数组返回。

在T[] toArray(T[] a) 方法中,需要传递一个T类型的数组对象,这样可以按照T类型返回T类型数组:

public  T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a;}

首先是判断传入的数组a的大小是否大于或者等于list的大小,如果小于,那么利用反射机制重新创建一个数组,此时参数a和toArray的数组就不是同一个数组了,如果a的大小大于size的值,假设数组a={"1

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

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

相关文章

C++多重继承与虚基类及与.NET的比较

多重继承前面我们介绍的派生类只有一个基类&#xff0c;称为单基派生或单一继承。在实际运用中&#xff0c;我们经常需要派生类同时具有多个基类&#xff0c;这种方法称为多基派生或多重继承。2.1 多重继承的声明&#xff1a;在 C 中&#xff0c;声明具有两个以上基类的派生类与…

Javascript的IE和Firefox兼容性汇编

window.event现有问题&#xff1a;使用 window.event 无法在 FF 上运行解决方法&#xff1a;FF 的 event 只能在事件发生的现场使用&#xff0c;此问题暂无法解决。可以这样变通&#xff1a;原代码(可在IE中运行)&#xff1a;<input type"button" name"someB…

Java Double类compareTo()方法与示例

双类compareTo()方法 (Double class compareTo() method) compareTo() method is available in java.lang package. compareTo()方法在java.lang包中可用。 compareTo() method is used to check equality or inequality for this Double-object against the given Double-obje…

平院实训门禁系统导入

这是我的配置&#xff08;如果是Win10最好每一步都管理员身份运行&#xff09; win7 SQLServer2008 VS2012 切记&#xff1a;注意&#xff1a;当你SQLserver创建数据库和VS连接数据库的时候得用同一种方式&#xff0c;要么都用window&#xff08;主机名&#xff09;&#xff0…

ffmpeg 解码音频(aac、mp3)输出pcm文件

ffmpeg 解码音频&#xff08;aac、mp3&#xff09;输出pcm文件 播放pcm可以参考&#xff1a; ffplay -ar 48000 -ac 2 -f f32le out.pcm main.c #include <stdio.h> #include <stdlib.h> #include <string.h>#include <libavutil/frame.h> #include …

Jquery getJSON()

getJSON与aspx 准备工作 Customer类 public class Customer{public int Unid { get; set; }public string CustomerName { get; set; }public string Memo { get; set; }public string Other { get; set; }}&#xff08;一&#xff09;ashx Customer customer new Customer { …

北京中信银行总行地址_中信银行拉萨分行举行“存款保险标识”启用和存款保险条例宣传活动...

11月NOV中信银行拉萨分行举行“存款保险标识”启用和《存款保险条例》宣传活动揭牌启用仪式111月Jul根据人民银行和总行关于“存款保险标识”启用工作相关要求&#xff0c;分行行领导高度重视“存款保险标识”启用和《存款保险条例》宣传活动工作&#xff0c;按照统一工作部署、…

Java ClassLoader getPackage()方法与示例

ClassLoader类的getPackage()方法 (ClassLoader Class getPackage() method) getPackage() method is available in java.lang package. getPackage()方法在java.lang包中可用。 getPackage() method is used to return the package that has been defined in ClassLoader or t…

C---编写程序:求出1~1000之间能被7或12整除,但不能同时被二者整除的所有整数,将结果保存在数组中,要求程序数据的输入、计算和输出均使用函数实现。

编写程序&#xff1a;求出1~1000之间能被7或12整除&#xff0c;但不能同时被二者整除的所有整数&#xff0c;将结果保存在数组中&#xff0c;要求程序数据的输入、计算和输出均使用函数实现。 编程思路&#xff1a;分别编写函数input()、cal()、output()实现数据的输入、计算和…

报告称我国成最大移民输出国 将形成投资产业链(关注)

时代特有的现象&#xff0c;我们应该予以关注 “现在国内房价这么高&#xff0c;政策也看不清&#xff0c;还不如逢高卖掉之前买的几套房子&#xff0c;一两套房子的钱办个投资移民&#xff0c;趁还年轻&#xff0c;拿到绿卡后享受一下美国本国待遇的高等教育了。”广州&#x…

转整型_156.Ruby烘焙大理石豆沙吐司解锁大理石花纹整型

好看又好吃的大理石豆沙面包。红豆馅均匀分布在松软细腻的面包体里&#xff0c;手撕着吃&#xff0c;一层层的甜美与温柔&#xff5e;关于吐司面包&#xff0c;我公众号里写过白吐司(基础款牛奶吐司&#xff0c;超绵鲜奶油吐司)和全麦吐司(基础款50%全麦吐司&#xff0c;经典燕…

ffmpeg 解码视频(h264、mpeg2)输出yuv420p文件

ffmpeg 解码视频&#xff08;h264、mpeg2&#xff09;输出yuv420p文件 播放yuv可以参考&#xff1a;ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 out.yuv main.c #include <stdio.h> #include <stdlib.h> #include <string.h>#includ…

VS2010 快捷键 (空格显示 绿点, Tab 显示箭头)

VS2010 有用的快捷键 &#xff1a; Ctrl r, ctrl w, 切换空格示。 转载于:https://www.cnblogs.com/fengye87626/archive/2012/11/21/2780716.html

C---编写程序:实现一个随堂测试,能进行加减乘除运算。要求如下:(1)随机产生两个1~10的正整数,在屏幕上输出题目,如:5+3=?(2)学生输入答案,程序检查学生输入答案是否正确,若正确,

编写程序&#xff1a;实现一个随堂测试&#xff0c;能进行加减乘除运算。要求如下&#xff1a; 1&#xff09;随机产生两个1~10的正整数&#xff0c;在屏幕上输出题目&#xff0c;如&#xff1a;53&#xff1f; 2&#xff09;学生输入答案&#xff0c;程序检查学生输入答案是…

分析一下mp4格式的trak -> mdia -> minf -> stbl -> stts、stsc 这两个box信息

分析一下mp4格式的trak -> mdia -> minf -> stbl -> stts、stsc 这两个box信息 &#xff08;因为这两个box在音频trak和视频trak 下都有的&#xff0c;而且都有一个数组的值是比较绕的&#xff09; 目录&#xff1a;stts&#xff1a;记录时间戳的&#xff0c;每个s…

利用SQL注入2分钟入侵网站

说起流光、溯雪、乱刀&#xff0c;可以说是大名鼎鼎无人不知无人不晓&#xff0c;这些都是小榕哥的作品。每次一提起小榕哥来&#xff0c;我的崇拜景仰就如滔滔江水&#xff0c;连绵不绝~~~~&#xff08;又来了&#xff01;&#xff09; 让我们崇拜的小榕哥最新又发布了SQL注入…

pip安装deb_技术|如何在 Ubuntu 上安装 pip

pip 是一个命令行工具&#xff0c;允许你安装 Python 编写的软件包。 学习如何在 Ubuntu 上安装 pip 以及如何使用它来安装 Python 应用程序。有许多方法可以在 Ubuntu 上安装软件。 你可以从软件中心安装应用程序&#xff0c;也可以从下载的 DEB 文件、PPA(LCTT 译注&#xff…

assubclass_Java类class asSubclass()方法及示例

assubclass类类asSubclass()方法 (Class class asSubclass() method) asSubclass() method is available in java.lang package. asSubclass()方法在java.lang包中可用。 asSubclass() method casts this Class object to denote a subclass of the class denoted by the given…

VB6.0 怎样启用控件comdlg32.ocx

VB6.0 怎样启用控件comdlg32.ocx 怎样启用控件comdlg32.ocx 2008-10-08 09:32 提问者&#xff1a; nefu_20061617 |浏览次数&#xff1a;1502次vbs文件中有代码Set ComDlg CreateObject("MSComdlg.CommonDialog")运行时发生错误ActiveX 部件不能创建对象: MSComdlg.…

Python---爬虫案例

例1、爬取公众号文章中的图片。 1&#xff0c;首先打开要获取公众号文章的地址 2&#xff0c;按下F12&#xff0c;再按Ctrl Shift C&#xff0c;然后鼠标移动到图片位置&#xff0c;然后观察控制台中显示图片对应的代码位置 3&#xff0c;分析该位置的代码段 代码段如下&…