一、LinkedList集合特点

问题 结      论
LinkedList是否允许空 允许
LinkedList是否允许重复数据 允许
LinkedList是否有序 有序
LinkedList是否线程安全 非线程安全

   LinkedList集合底层是由双向链表组成,而不是双向循环链表。

有一个头结点和一个尾结点,我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。

二、LinkedList集合底层实现

1.什么是链表

链表原是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储结构还存放有其前一个存储单元的地址),每次查找数据的时候,通过某个存储单元中的下一个存储单元的地址寻找其后面的那个存储单元。

2. 什么是双向链表

双向链表也是链表的一种,它每个数据结点中都有两个结点,分别指向其直接前驱和直接后继。所以我们从双向链表的任意一个结点开始都可以很方便的访问其前驱元素和后继元素。

3.双向链表的定义

双向链表也是链表的一种,它每个数据结点中都有两个结点,分别指向其直接前驱和直接后继。所以我们从双向链表的任意一个结点开始都可以很方便的访问其前驱元素和后继元素。

4.双向链表的存储结构

双向链表也是采用的链式存储结构,它与单链表的区别就是每个数据结点中多了一个指向前驱元素的指针域 ,它的存储结构如下图

当双向链表只有一个结点的时候它的存储结构如下:

头节点和尾结点指向同一个元素。

三.LinkedList源码分析

1.构造方法,无参构造,有参构造。

 public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
// 调用无参构造函数
this();
// 添加集合中所有的元素
addAll(c);
}

2.Node结点组成

 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;
}
}

3.添加元素 add方法

 public boolean add(E e) {
linkLast(e);// // 添加元素到末尾
return true;
}
void linkLast(E e) {
// 保存尾结点,l为final类型,不可更改
final Node<E> l = last;
// 新生成结点的前驱为l,后继为null
final Node<E> newNode = new Node<>(l, e, null);
// 重新赋值尾结点
last = newNode;
if (l == null) // 尾结点为空
first = newNode; // 赋值头结点
else // 尾结点不为空
l.next = newNode; // 尾结点的后继为新生成的结点
// 大小加1
size++;
// 结构性修改加1
modCount++;
}

3.查询元素E get(int index)获取指定节点数据

 public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// 判断插入的位置在链表前半段或者是后半段
if (index < (size >> 1)) { // 插入位置在前半段
Node<E> x = first;
for (int i = 0; i < index; i++) // 从头结点开始正向遍历
x = x.next;
return x; // 返回该结点
} else { // 插入位置在后半段
Node<E> x = last;
for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
x = x.prev;
return x; // 返回该结点
}
}

4.remove移除结点时

 E unlink(Node<E> x) {
// 保存结点的元素
final E element = x.item;
// 保存x的后继
final Node<E> next = x.next;
// 保存x的前驱
final Node<E> prev = x.prev; 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--;
// 结构性修改加1
modCount++;
// 返回结点的旧元素
return element;
}

 四.手写LinkedList代码:

手写源码主要是为了便于对LinkedList集合的更好理解,主要对add(),remove,add(index,element) ,get(i)这些方法进行简单的写法,写法比较通俗更便于理解。具体可以参考下面的代码。

1.自定义LinkedList集合。

 package com.zzw.cn.springmvc.linkList;

 /**
* @author Simple
* @date 16:58 2019/9/5
* @description 手写LinkList集合
* 思路:
* 1.集合add
* 2.remove
* 3.add(index,element)
*/ public class AnLinkeList<E> {
private Node first;//第一个节点值
private Node last;//最后一个节点值
int size;//集合长度 /**
* 添加
*
* @param e
*/
public void add(E e) {
Node node = new Node();
//增加时判断集合是否为null 1.为null时候位置指向不同
node.object = e;
if (null == first) { //第一个元素添加是第一个节点和最后一个节点指向位置都为第一个
first = node;
} else {
//存放上一个节点内容
node.prev = last;
//将上一个节点值指向当前节点
last.next = node;
}
//对最后一个节点赋值
last = node;
size++;
} public E get(int index) {
Node node = null;
if (null != first) {
node = first;
//循环遍历指向最后一个节点
for (int i = 0; i < index; i++) {
node = node.next;
}
return (E) node.object;
}
return null;
} public Node getNode(int index) {
Node node = null;
if (null != first) {
node = first;
//循环遍历指向最后一个节点
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
return null;
} //删除
public void remove(int index) {
//1找到当前元素 删除时,将当前元素B的pre指向上一个A节点,将上一个元素A的节点指向当前元素B的next节点。
checkElementIndex(index);
Node node = getNode(index);
Node prevNode = node.prev;
Node nextNode = node.next;
if (prevNode.next != null) {
prevNode.next = nextNode;
}
if (nextNode != null) {
nextNode.prev = prevNode;
}
size--; } private boolean isElementIndex(int index) {
return index >= 0 && index <= size;
} private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException("越界啦!");
} private void add(int index, E e) {
checkElementIndex(index);
if (index == size)
add(e);
else
addEle(index, e); } private void addEle(int index, E e) {
/**
*
* 新增E 上一个节点是A 下一个节点是B
* A->next=E
* E->pre=A
* E->next=B
* B->pre=E
*/
Node newNode = new Node();
newNode.object = e;
Node oldNode = getNode(index);
Node oldPrev = oldNode.prev;
//当前节点上一个对应新节点
oldNode.prev = newNode;
//如果当前节点为第一个时候
if (oldPrev == null) {
first = newNode;
} else {
//新节点的下一个对应当前节点
oldPrev.next = newNode;
}
//新节点的上一个对应老节点之前的
newNode.prev = oldPrev;
//老节点的下一个对应新节点
newNode.next = oldNode;
size++; } }

 2.节点的定义

 package com.zzw.cn.springmvc.linkList;

 /**
* @author Simple
* @date 17:01 2019/9/5
* @description 定义节点
*/
public class Node {
Node prev;//上一个节点
Node next;//下一个节点
Object object;//节点值
}

 3.测试类的编写。

 package com.zzw.cn.springmvc.linkList;

 /**
* @author Simple
* @date 19:45 2019/9/5
* @description
*/
public class TestLinkeList {
public static void main(String[] args) {
AnLinkeList<String> list = new AnLinkeList<>();
list.add("1");
list.add("2");
list.add("3");
System.out.println("add方法后结果");
for (int i = 0; i < list.size; i++) {
System.out.print(list.get(i)+" ");
}
System.out.println();
list.remove(2);
System.out.println("remove方法后结果");
for (int i = 0; i < list.size; i++) {
System.out.print(list.get(i)+" ");
}
System.out.println("根据索引值添加的");
list.add(1,"3");
for (int i = 0; i < list.size; i++) {
System.out.print(list.get(i)+" ");
}
}
}

4.运行结果

五、LinkedList和ArrayList的对比

1、顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置塞一个数据就好了;

LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList

2、基于上一点,因为LinkedList里面不仅维护了待插入的元素,还维护了Entry的前置Entry和后继Entry,如果一个LinkedList中的Entry非常多,那么LinkedList将比ArrayList更耗费一些内存

3、有些说法认为LinkedList做插入和删除更快,这种说法其实是不准确的:

(1)LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址

(2)ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址

(一)LinkedList集合解析及手写集合的更多相关文章

  1. 性能学习笔记之四--事务,思考时间,检查点,集合点和手写lr接口

    一.事物,思考时间,检查点,集合点 1.事务 lr里面的事物是lr运行脚本的基础.lr里面 要测试的三个维度都以事物为单位,所以一定要有事物.事务的概念贯穿loadrunner的使用,比如我们说的响应 ...

  2. 2 手写Java LinkedList核心源码

    上一章我们手写了ArrayList的核心源码,ArrayList底层是用了一个数组来保存数据,数组保存数据的优点就是查找效率高,但是删除效率特别低,最坏的情况下需要移动所有的元素.在查找需求比较重要的 ...

  3. 第4.4节 Python解析与推导:列表解析、字典解析、集合解析

    一.    引言 经过前几个章节的介绍,终于把与列表解析的前置内容介绍完了,本节老猿将列表解析.字典解析.集合解析进行统一的介绍. 前面章节老猿好几次说到了要介绍列表解析,但老猿认为涉及知识层面比较多 ...

  4. 【Machine Learning in Action --2】K-近邻算法构造手写识别系统

    为了简单起见,这里构造的系统只能识别数字0到9,需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素的黑白图像.尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理 ...

  5. LoadRunner手写脚本、检查点、集合点、事务、思考时间

    手写脚本 什么时候要手写? 可以有条件手写脚本的场景有两类: 有接口说明文档 没有借口说明文档,要去录制,录制不了,抓包手写 所需函数 我们这里讲的例子是基于 http 协议的,也是常见的两种请求类型 ...

  6. map,set,list等集合解析以及HashMap,LinkedHashMap,TreeMap等该选谁的的区别

    前言: 今天在整理一些资料时,想起了map,set,list等集合,于是就做些笔记,提供给大家学习参考以及自己日后回顾. Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允 ...

  7. 【Java】 ArrayList和LinkedList实现(简单手写)以及分析它们的区别

    一.手写ArrayList public class ArrayList { private Object[] elementData; //底层数组 private int size; //数组大小 ...

  8. Atiit 如何手写词法解析器

    Atiit 如何手写词法解析器 1.1. 通过编程直接从正则->nfa->dfa->表驱动词法解析一条龙自动生成.那是用程序自动生成是需要这样的,自己手写完全不必要这么复杂1 1.2 ...

  9. c#集合解析

    什么是集合(collection)? 提供了一种结构化组织任意对象的方式,从.NET 的角度看,所谓的集合可以定义为一种对象,这种对象实现一个或者多个System.Collections.IColle ...

随机推荐

  1. Oracle JDK与OpenJDK到底有什么不同?

    ​不知道各位developer平时是否有过疑问,Oracle JDK是什么,OpenJDK又是什么? Oracle JDK便是平常我们在windows系统上做开发使用的JDK,又称作SUN JDK.O ...

  2. Java NIO学习系列六:Java中的IO模型

    前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...

  3. Linux基本操作及安装(部分)

    1.分别用cat \tac\nl三个命令查看文件/etc/ssh/sshd_config文件中的内容,   并用自己的话总计出这三个文档操作命令的不同之处? [root@localhost ~]# c ...

  4. spring boot 学习笔记(二)之打包

    一.叙述 spring boot 在 pom 中可以配置成  packaging 为 jar ,这样打包出来的就是一个 jar 包,可以通过 Java 命令直接运行, Java 命令为: java - ...

  5. 认识 tomcat 被占用问题

    (1) Server 中的 port 该端口为tomcat使用jvm的端口,必须保证唯一性,否则tomcat启动不成功: (2) Connector 中的 port 该端口为tomcat中所有web应 ...

  6. PythonDay03

    ## 第三章 ### 今日内容 1.整型 2.布尔值 3.字符串 ​ 索引​ 切片​ 步长​ 字符串的方法 4.for循环 ### 1.整型 - python3:全部是整形- python2:整形,长 ...

  7. 【React踩坑记三】React项目报错Can't perform a React state update on an unmounted component

    意思为:我们不能在组件销毁后设置state,防止出现内存泄漏的情况 分析出现问题的原因: 我这里在组件加载完成的钩子函数里调用了一个EventBus的异步方法,如果监听到异步方法,则会更新state中 ...

  8. Go中的异常处理

    1. errors包 Go 有一个预先定义的 error 接口类型 : type error interface { Error() string } 错误值用来表示异常状态.Go也提供了一个包:er ...

  9. Meta 用法汇总

    本文引自: http://blog.csdn.net/MR_LP/article/details/53607087 什么是 meta ? meta 是html语言head区的一个辅助性标签.也许你认为 ...

  10. java8(二)方法引用

    方法引用让你可以重复使用现有的方法定义,并像 Lambda 一样进行传递. 方法引用可以被看作仅仅调用特定方法的 Lambda 的一种快捷写法. 事实上,方法引用就是让你根据已有的方法实现来创建 La ...