把两个java集合里的数据一一对应_Java集合之LinkedList,数据结构原理,与ArrayList的区别对比...

一、LinkedList概述

1.初识LinkedList

LinkedList是基于链表实现的,所以先讲解一下什么是链表。链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储结构还存放有其前一个存储单元的地址),每次查找数据的时候,通过某个存储单元中的下一个存储单元的地址寻找其后面的那个存储单元。

这么讲可能有点抽象,先提一句,LinkedList是一种双向链表,双向链表我认为有两点含义:

1、链表中任意一个存储单元都可以通过向前或者向后寻址的方式获取到其前一个存储单元和其后一个存储单元

2、链表的尾节点的后一个节点是链表的头结点,链表的头结点的前一个节点是链表的尾节点

2.LinkedList数据结构原理

LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:

2c2fcd77a33d1aff88d2d0001396e7f2.png

既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据、前一个节点的位置信息和后一个节点位置信息,如下图所示:

ca2cf59bc2b49a8704f9ef399da850d6.png

3.私有属性

LinkedList中定义了两个私有属性:

private transient Entry header = new Entry(null, null, null);private transient int size = 0;

header是双向链表的头节点,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next,element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。

size是双向链表中节点实例的个数。

首先来了解节点类Entry类的代码。

private static class Entry { E element; Entry next; Entry previous; Entry(E element, Entry next, Entry previous) { this.element = element; this.next = next; this.previous = previous; }}

看到LinkedList的Entry中的"E element",就是它真正存储的数据。"Entry next"和"Entry previous"表示的就是这个存储单元的前一个存储单元的引用地址和后一个存储单元的引用地址。用图表示就是:

4ed769ae19e657990e81ac5dd5d91781.png

4.构造函数

LinkedList提供了两个构造方法,如下所示:

public LinkedList() { header.next = header.previous = header;}public LinkedList(Collection extends E> c) { this(); addAll(c);}

第一个构造方法不接受参数,将header实例的previous和next全部指向header实例(注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null),这样整个链表其实就只有header一个节点,用于表示一个空的链表。

执行完构造函数后,header实例自身形成一个闭环,如下图所示:

第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。

9ed602ccf617fe5911ea261dc8259c2e.png

5.四个关注点在LinkedList上的答案

关 注 点结 论LinkedList是否允许空允许LinkedList是否允许重复数据允许LinkedList是否有序有序LinkedList是否线程安全非线程安全

9dc8b9570b32aad71eed409dfd545617.png

二、添加元素

首先看下LinkedList添加一个元素是怎么做的,假如我有一段代码:

1 public static void main(String[] args)2 {3 List list = new LinkedList();4 list.add("111");5 list.add("222");6 }

逐行分析main函数中的三行代码是如何执行的,首先是第3行,看一下LinkedList的源码:

 1 public class LinkedList 2 extends AbstractSequentialList 3 implements List, Deque, Cloneable, java.io.Serializable 4 { 5 private transient Entry header = new Entry(null, null, null); 6 private transient int size = 0; 7  8 /** 9 * Constructs an empty list.10 */11 public LinkedList() {12 header.next = header.previous = header;13 }14 ...15 }

看到,new了一个Entry出来名为header,Entry里面的previous、element、next都为null,执行构造函数的时候,将previous和next的值都设置为header的引用地址,还是用画图的方式表示。32位JDK的字长为4个字节,而目前64位的JDK一般采用的也是4字长,所以就以4个字长为单位。header引用地址的字长就是4个字节,假设是0x00000000,那么执行完"List list = new LinkedList()"之后可以这么表示:

d7d4b5be152a7967c19172585bc2b472.png

接着看第4行add一个字符串"111"做了什么:

1 public boolean add(E e) {2 addBefore(e, header);3 return true;4 }
1 private Entry addBefore(E e, Entry entry) {2 Entry newEntry = new Entry(e, entry, entry.previous);3 newEntry.previous.next = newEntry;4 newEntry.next.previous = newEntry;5 size++;6 modCount++;7 return newEntry;8 }

addBefore(E e,Entry entry)方法是个私有方法,所以无法在外部程序中调用(当然,这是一般情况,你可以通过反射上面的还是能调用到的)。

addBefore(E e,Entry entry)先通过Entry的构造方法创建e的节点newEntry(包含了将其下一个节点设置为entry,上一个节点设置为entry.previous的操作,相当于修改newEntry的“指针”),之后修改插入位置后newEntry的前一节点的next引用和后一节点的previous引用,使链表节点间的引用关系保持正确。之后修改和size大小和记录modCount,然后返回新插入的节点。

第2行new了一个Entry出来,可能不太好理解,根据Entry的构造函数,我把这句话"翻译"一下,可能就好理解了:

1、newEntry.element = e;

2、newEntry.next = header;

3、newEntry.previous = header.previous;

header的引用地址为0x00000000,header.previous上图中已经看到了,也是0x00000000,那么假设new出来的这个Entry的地址是0x00000001,继续画图表示:

38b01bcc2b13feb68bf8bda2a5c90cd1.png

一共五步,每一步的操作步骤都用数字表示出来了:

1、新的entry的element赋值为111;

2、新的entry的next是header的引用地址,header的引用地址是0x00000000,所以新的entry的next即0x00000000;

3、新的entry的previous是header的previous,header的previous是0x00000000,所以新的entry的next即0x00000000;

4、"newEntry.previous.next = newEntry",首先是newEntry的previous,由于newEntry的previous为0x00000000,所以newEntry.previous表示的是header,header的next为newEntry,即header的next为0x00000001;

5、"newEntry.next.previous = newEntry",和4一样,把header的previous设置为0x00000001;

b0c2c9041d20c2d9ea3b1f0261bd6d16.png

为什么要这么做?还记得双向链表的两个特点吗,一是任意节点都可以向前和向后寻址,二是整个链表头的previous表示的是链表的尾Entry,链表尾的next表示的是链表的头Entry。现在链表头就是0x00000000这个Entry,链表尾就是0x00000001,可以自己看图观察、思考一下是否符合这两个条件。

最后看一下add了一个字符串"222"做了什么,假设新new出来的Entry的地址是0x00000002,画图表示:

6aa30c287a7ca573742b18b52b441916.png

还是执行的那5步,图中每一步都标注出来了,只要想清楚previous、next各自表示的是哪个节点就不会出问题了。

至此,往一个LinkedList里面添加一个字符串"111"和一个字符串"222"就完成了。从这张图中应该理解双向链表比较容易:

1、中间的那个Entry,previous的值为0x00000000,即header;next的值为0x00000002,即tail,这就是任意一个Entry既可以向前查找Entry,也可以向后查找Entry。

2、头Entry的previous的值为0x00000002,即tail,这就是双向链表中头Entry的previous指向的是尾Entry。

3、尾Entry的next的值为0x00000000,即header,这就是双向链表中尾Entry的next指向的是头Entry。

三、查看元素

看一下LinkedList的代码是怎么写的:

public E get(int index) { return entry(index).element;} 1 // 获取双向链表中指定位置的节点  2 private Entry entry(int index) {  3 if (index < 0 || index >= size)  4 throw new IndexOutOfBoundsException("Index: "+index+  5 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值