作者:Java知音-微笑面对生活

概述

LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的,是线程不安全的,允许元素为null的双向链表。

源码分析

1. 变量

/*** 集合元素数量**/
transient int size = 0;/*** 指向第一个节点的指针* Invariant: (first == null && last == null) ||*            (first.prev == null && first.item != null)*/
transient Node<E> first;/*** 指向最后一个节点的指针* Invariant: (first == null && last == null) ||*            (last.next == null && last.item != null)*/
transient Node<E> last;

2. 构造方法

/*** 无参构造方法*/
public LinkedList() {
}/*** 将集合c所有元素插入链表中*/
public LinkedList(Collection<? extends E> c) {this();addAll(c);
}

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

因为一个Node既有prev也有next,所以证明它是一个双向链表。

4. 添加元素

addAll(Collection<? extends E> c)

将集合c添加到链表,如果不传index,则默认是添加到尾部。如果调用addAll(int index, Collection<? extends E> c)方法,则添加到index后面。

/*** 将集合添加到链尾*/
public boolean addAll(Collection<? extends E> c) {return addAll(size, c);
}/** * */
public boolean addAll(int index, Collection<? extends E> c) {checkPositionIndex(index);// 拿到目标集合数组Object[] a = c.toArray();//新增元素的数量int numNew = a.length;//如果新增元素数量为0,则不增加,并返回falseif (numNew == 0)return false;//定义index节点的前置节点,后置节点Node<E> pred, succ;// 判断是否是链表尾部,如果是:在链表尾部追加数据//尾部的后置节点一定是null,前置节点是队尾if (index == size) {succ = null;pred = last;} else {// 如果不在链表末端(而在中间部位)// 取出index节点,并作为后继节点succ = node(index);// index节点的前节点 作为前驱节点pred = succ.prev;}// 链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作for (Object o : a) {@SuppressWarnings("unchecked") // 类型转换E e = (E) o;// 前置节点为pred,后置节点为null,当前节点值为e的节点newNodeNode<E> newNode = new Node<>(pred, e, null);// 如果前置节点为空, 则newNode为头节点,否则为pred的next节点if (pred == null)first = newNode;elsepred.next = newNode;pred = newNode;}// 循环结束后,如果后置节点是null,说明此时是在队尾追加的if (succ == null) {// 设置尾节点last = pred;} else {//否则是在队中插入的节点 ,更新前置节点 后置节点pred.next = succ;succ.prev = pred;}// 修改数量sizesize += numNew;//修改modCountmodCount++;return true;
}/*** 取出index节点*/
Node<E> node(int index) {// assert isElementIndex(index);// 如果index 小于 size/2,则从头部开始找if (index < (size >> 1)) {// 把头节点赋值给xNode<E> x = first;for (int i = 0; i < index; i++)// x=x的下一个节点x = x.next;return x;} else {// 如果index 大与等于 size/2,则从后面开始找Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}// 检测index位置是否合法
private void checkPositionIndex(int index) {if (!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}// 检测index位置是否合法
private boolean isPositionIndex(int index) {return index >= 0 && index <= size;
}

假设我们要在index=2处添加{1,2}到链表中,图解如下:

第一步:拿到index=2的前驱节点 prev=ele1

第二步:遍历集合prev.next=newNode,并实时更新prev节点以便下一次
遍历:prev=newNode

第三步:将index=2的节点ele2接上:prev.next=ele2,ele2.prev=prev

注意node(index)方法:寻找处于index的节点,有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。

addFirst(E e)方法

将e元素添加到链表并设置其为头节点(first)。

public void addFirst(E e) {linkFirst(e);
}//将e链接成列表的第一个元素
private void linkFirst(E e) {final Node<E> f = first;// 前驱为空,值为e,后继为ffinal Node<E> newNode = new Node<>(null, e, f);first = newNode;//若f为空,则表明列表中还没有元素,last也应该指向newNodeif (f == null)last = newNode;else//否则,前first的前驱指向newNodef.prev = newNode;size++;modCount++;
}
  1. 拿到first节点命名为f
  2. 新创建一个节点newNode设置其next节点为f节点
  3. 将newNode赋值给first
  4. 若f为空,则表明列表中还没有元素,last也应该指向newNode;否则,前first的前驱指向newNode。
  5. 图解如下:


addLast(E e)方法

将e元素添加到链表并设置其为尾节点(last)。

public void addLast(E e) {linkLast(e);
}
/*** 将e链接成列表的last元素*/
void linkLast(E e) {final Node<E> l = last;// 前驱为前last,值为e,后继为nullfinal Node<E> newNode = new Node<>(l, e, null);last = newNode;//最后一个节点为空,说明列表中无元素if (l == null)//first同样指向此节点first = newNode;else//否则,前last的后继指向当前节点l.next = newNode;size++;modCount++;
}

过程与linkFirst()方法类似,这里略过。

add(E e)方法

在尾部追加元素e。

public boolean add(E e) {linkLast(e);return true;
}void linkLast(E e) {final Node<E> l = last;// 前驱为前last,值为e,后继为nullfinal Node<E> newNode = new Node<>(l, e, null);last = newNode;//最后一个节点为空,说明列表中无元素if (l == null)//first同样指向此节点first = newNode;else//否则,前last的后继指向当前节点l.next = newNode;size++;modCount++;
}
add(int index, E element)方法

在链表的index处添加元素element.

public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));
}
/*** 在succ节点前增加元素e(succ不能为空)*/
void linkBefore(E e, Node<E> succ) {// assert succ != null;// 拿到succ的前驱final Node<E> pred = succ.prev;// 新new节点:前驱为pred,值为e,后继为succfinal Node<E> newNode = new Node<>(pred, e, succ);// 将succ的前驱指向当前节点succ.prev = newNode;// pred为空,说明此时succ为首节点if (pred == null)// 指向当前节点first = newNode;else// 否则,将succ之前的前驱的后继指向当前节点pred.next = newNode;size++;modCount++;
}

linkLast方法上文有讲。
linkBefore(E e, Node<E> succ)方法步骤:

  1. 拿到succ的前驱节点
  2. 新new节点:前驱为pred,值为e,后继为succ : Node<>(pred, e, succ);
  3. 将succ的前驱指向当前节点
  4. pred为空,说明此时succ为首节点,first指向当前节点;否则,将succ之前的前驱的后继指向当前节点

5. 获取/查询元素

get(int index)方法

根据索引获取链表中的元素。

public E get(int index) {checkElementIndex(index);return node(index).item;
}// 检测index合法性
private void checkElementIndex(int index) {if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}// 根据index 获取元素
Node<E> node(int index) {// assert isElementIndex(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;}
}

node方法上文有详细讲解,这里不做介绍。

getFirst()方法

获取头节点。

public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;
}
getLast()方法

获取尾节点。

public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;
}

6. 删除元素

remove(Object o)

根据Object对象删除元素。

public boolean remove(Object o) {// 如果o是空if (o == null) {// 遍历链表查找 item==null 并执行unlink(x)方法删除for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;
}E unlink(Node<E> x) {// assert x != null;// 保存x的元素值final E element = x.item;//保存x的后继final Node<E> next = x.next;//保存x的前驱final Node<E> prev = x.prev;//如果前驱为null,说明x为首节点,first指向x的后继if (prev == null) {first = next;} else {//x的前驱的后继指向x的后继,即略过了xprev.next = next;// x.prev已无用处,置空引用x.prev = null;}// 后继为null,说明x为尾节点if (next == null) {// last指向x的前驱last = prev;} else {// x的后继的前驱指向x的前驱,即略过了xnext.prev = prev;// x.next已无用处,置空引用x.next = null;}// 引用置空x.item = null;size--;modCount++;// 返回所删除的节点的元素值return element;
}
  1. 遍历链表查找 item==null 并执行unlink(x)方法删除
  2. 如果前驱为null,说明x为首节点,first指向x的后继,x的前驱的后继指向x的后继,即略过了x.
  3. 如果后继为null,说明x为尾节点,last指向x的前驱;否则x的后继的前驱指向x的前驱,即略过了x,置空x.next
  4. 引用置空:x.item = null
  5. 图解:
remove(int index)方法

根据链表的索引删除元素。

public E remove(int index) {checkElementIndex(index);//node(index)会返回index对应的元素return unlink(node(index));
}E unlink(Node<E> x)  方法上文有详解。
removeFirst()方法

删除头节点。

public E removeFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return unlinkFirst(f);
}private E unlinkFirst(Node<E> f) {// assert f == first && f != null;//取出首节点中的元素final E element = f.item;//取出首节点中的后继final Node<E> next = f.next;f.item = null;f.next = null; // help GC// first指向前first的后继,也就是列表中的2号位first = next;//如果此时2号位为空,那么列表中此时已无节点if (next == null)//last指向nulllast = null;else// 首节点无前驱 next.prev = null;size--;modCount++;return element;
}

原理与添加头节点类似。

removeLast()方法

删除尾节点(last)

public E removeLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return unlinkLast(l);
}private E unlinkLast(Node<E> l) {// assert l == last && l != null;// 取出尾节点中的元素final E element = l.item;// 取出尾节点中的后继final Node<E> prev = l.prev;l.item = null;l.prev = null; // help GC// last指向前last的前驱,也就是列表中的倒数2号位last = prev;// 如果此时倒数2号位为空,那么列表中已无节点if (prev == null)// first指向nullfirst = null;else// 尾节点无后继prev.next = null;size--;modCount++;// 返回尾节点保存的元素值return element;
}

7. 修改元素

修改元素比较简单,先找到index对应节点,然后对值进行修改。

public E set(int index, E element) {checkElementIndex(index);// 获取到需要修改元素的节点Node<E> x = node(index);// 保存之前的值E oldVal = x.item;// 执行修改x.item = element;// 返回旧值return oldVal;
}

8. 与ArrayList的对比

优点:

  1. 不需要扩容和预留空间,空间效率高
  2. 增删效率高

缺点:

  1. 随机访问时间效率低
  2. 改查效率低

面试必会之LinkedList源码分析相关推荐

  1. 面试必会之ArrayList源码分析手写ArrayList

    作者:Java知音-微笑面对生活 简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于 ...

  2. 面试必会之HashMap源码分析

    简介 HashMap最早出现在JDK1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值,是非线程安全类,在多线程环境下可能会存在问题. 1.8版本的HashMap数据结 ...

  3. LinkedList 源码分析

    前言 上篇文章分析了 ArrayList 的源码,面试过程中经常会被面试官来问 LinkedList 和 ArrayList 的区别,这篇文章从源码的角度来看下 LinkedList 以后,再和上篇文 ...

  4. java linkedlist源码_Java集合之LinkedList源码分析

    一.LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的. ps:这里有一个问题,就是关于实现LinkedList的数据结构是否 ...

  5. List接口的常用方法以及ArrayList/LinkedList源码分析

    1.List接口的常用方法 ArrayList list = new ArrayList();list.add(123);list.add(456);list.add("AA"); ...

  6. 数组、链表、LinkedList源码分析、跳表

    一.数组 1.什么是数组 数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据 线性表:数据排成像一条线一样的结构.每个线性表上的数据最多只有前和后两个方向.其实除了数组,链 ...

  7. LinkedList 源码分析(JDK 1.8)

    1.概述 LinkedList 是 Java 集合框架中一个重要的实现,其底层采用的双向链表结构.和 ArrayList 一样,LinkedList 也支持空值和重复值.由于 LinkedList 基 ...

  8. Java集合篇:LinkedList源码分析

    (注:本文内容基于JDK1.6) 一.概述: LinkedList与ArrayList一样实现List接口,只是ArrayList是List接口的大小可变数组的实现,LinkedList是List接口 ...

  9. Java类集框架 —— LinkedList源码分析

    在JDK1.7之前,LinkedList是采用双向环形链表来实现的,在1.7及之后,Oracle将LinkedList做了优化,将环形链表改成了线性链表.本文对于LinkedList的源码分析基于JD ...

最新文章

  1. Maxim and Biology
  2. nginx log_format 中的变量
  3. Python装饰器学习笔记
  4. NativeScript - JS 构建跨平台的原生 APP
  5. java学习(82):静态代码块内部类
  6. 如何写计算机会议的rebuttal
  7. 手工机器人的做法大全用易拉罐_纯手工自制黄油,做法简单详细易操作,蛋糕、饼干、面包都能用...
  8. apache负载均衡 健康检查_Nginx负载均衡之健康检查
  9. ASP.NET 3.5中的ListView控件和DataPager控件(二)
  10. idea中,springboot项目部署到docker
  11. 汇编语言第三章检测题
  12. linux下RRDTool安装方法
  13. 校园失物招领小程序 开题报告(基于微信小程序毕业设计题目选题课题)
  14. android的otg功能,怎么打开手机OTG功能?
  15. TalkingData的使用,iOS数据统计
  16. 计算机vb里代码里的双引号,在VB中使用字符串中的左双引号
  17. 刘顺琦 - CSCI 561 mid 1definition
  18. 元素地球化学类毕业论文文献有哪些?
  19. Inventor(C#)开发学习小结——入门篇
  20. #include指令引号与尖括号的区别

热门文章

  1. 华为否认降低手机产量传闻:全球生产水平正常 无明显调整
  2. 36岁程序员:领导平时称兄道弟,裁员时立刻变脸,看透人性
  3. 串口的输出设置【原创】
  4. php 添加 redis 扩展模块
  5. 3.请求安全-- 结合使用的安全优势总结
  6. 微型计算机接口技术2018真题,2018年微机原理及接口技术复习题.doc
  7. linux分区par,linux基础篇(磁盘分区)
  8. oracle dba收入水平,oracle教程_oracle dba 收入
  9. 递增的整数序列链表的插入_LeetCode基础算法题第178篇:和为零的N个唯一整数
  10. python重新安装_重新安装python