文章推荐

  • 精选java等全套学习资源
  • 精选java电子图书资源
  • 精选大数据学习资源
  • java项目练习精选

###一. 概述 LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。 除了 LIst 接口之外,LinkedList 还实现了 Deque,Cloneable,Serializable 三个接口。这说明该数据结构支持队列,克隆和序列化操作的。与 ArrayList 一样,允许 null 元素的存在,且是不支持多线程的。 ###二. 源码解读

##属性

LinkedList 提供了以下三个成员变量。size,first,last。

 transient int size = 0;transient Node<E> first;transient Node<E> last;

其中 size 为 LinkedList 的大小,first 和 last 分别为链表的头结点和尾节点。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 是 LInkedList 的内部类,定义了存储的数据元素,前一个节点和后一个节点,典型的双链表结构。

##构造方法

public LinkedList() {}
public LinkedList(Collection<? extends E> c) {this();addAll(c);
}

LinkedList 提供了两个构造方法:LinkedList() 和 LinkedList(Collection<? extends E> c)
LinkedList() 仅仅构造一个空的列表,没有任何元素。size = 0。first 和 last 都为 null。
后一个构造方法构造一个包含指定 Collection 中所有元素的列表,该构造方法首先会调用空的构造方法,然后通过 addAll() 的方式把 Collection 中的所有元素添加进去。

  • 调用 addAll() 方法,传入当前的节点个数 size,此时 size 为
  • 检查 index 是否越界
  • 将 collection 转换成数组
  • 遍历数组,将数组里面的元素创建为节点,并按照顺序连起来。
  • 修改当前的节点个数 size 的值
  • 操作次数 modCount 自增 1.
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;if (numNew == 0)return false;Node<E> pred, succ;if (index == size) {succ = null;pred = last;} else {succ = node(index);pred = succ.prev;}for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;Node<E> newNode = new Node<>(pred, e, null);if (pred == null)first = newNode;elsepred.next = newNode;pred = newNode;}if (succ == null) {last = pred;} else {pred.next = succ;succ.prev = pred;}size += numNew;modCount++;return true;
}

##add 操作

添加元素到链表末尾

public boolean add(E e) {linkLast(e);return true;
}void linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);last = newNode;if (l == null)first = newNode;elsel.next = newNode;size++;modCount++;
}

add 方法直接调用了 linkLast 方法,而 linkLast 方法是不对外开放的。该方法做了三件事情,新增一个节点,改变其前后引用,将 size 和 modCount 自增 1。其中 modCount 是记录对集合操作的次数。
在指定的位置插入元素

public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index));
}private void checkPositionIndex(int index) {if (!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {return index >= 0 && index <= size;
}void linkBefore(E e, Node<E> succ) {// assert succ != null;final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);succ.prev = newNode;if (pred == null)first = newNode;elsepred.next = newNode;size++;modCount++;
}

首先检查下标是否越界,然后判断如果 index == size 则添加到末尾,否则将该元素插入的 index 的位置。其中 node(index) 是获取 index 位置的节点,linkBefore 负责把元素 e 插入到 succ 之前。

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() 方法这里写的还是挺赞的,不是傻乎乎的从头到尾或者从尾到头遍历链表,而是将 index 与 当前链表的一半做对比,比一半小从头遍历,比一半大从后遍历。对于数据量很大时能省下不少时间。

##get 操作

很简单,首先获取节点,然后返回节点的数据即可。

public E get(int index) {checkElementIndex(index);return node(index).item;
}

##remove 操作

移除指定位置的元素

public E remove(int index) {checkElementIndex(index);return unlink(node(index));
}E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;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--;modCount++;return element;
}

先检查下标是否越界,然后调用 unlink 释放节点。
移除指定元素

public boolean remove(Object o) {if (o == null) {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;
}

判断要移除的元素是否为 null,然后在遍历链表,找到钙元素第一次出现的位置,移除并返回 true。
像其他的常用方法如:getFirst, getLast, removeFirst, removeLast, addFirst, addLast 等都很简单,扫一眼源码就能懂,我这里就不写了。

迭代器

LInkedList 的 iterator() 方法是在其父类 AbstractSequentialList 中定义的,最终一路 debug 到 LinkedList 类这里。其中 index 为 零。

public ListIterator<E> listIterator(int index) {checkPositionIndex(index);return new ListItr(index);
}

我们来看看 ListItr。

private Node<E> lastReturned;private Node<E> next;private int nextIndex;private int expectedModCount = modCount;ListItr(int index) {// assert isPositionIndex(index);next = (index == size) ? null : node(index);nextIndex = index;}public boolean hasNext() {return nextIndex < size;}public E next() {checkForComodification();if (!hasNext())throw new NoSuchElementException();lastReturned = next;next = next.next;nextIndex++;return lastReturned.item;}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

篇幅有限 ,我就只贴主要代码了。由源码可以看出初始化 ListItr 时,将 nextIndex 指向 index, 也就是零。如果该集合为空,那么 index == size 为 true,next 指向 null,否则 next 指向下标为零的元素,也就是第一个。
hasNext 直接返回 nextIndex < size 简单明了。下面看看 next 方法,首先检查 expectedModCount 与 modCount 是否相等,看似无关紧要的代码保证了集合在迭代过程中不被修改[包括新增删除节点等]。然后将 lastReturned 指向 next,next 后移一个节点,nextIndex 自增 1,并返回 lastReturned 节点的元素。
###总结

1、从源码可以看出 LinkedList 是基于链表实现的。如下图:

2、在查找和删除某元素时,区分该元素为 null和不为 null 两种情况来处理,LinkedList 中允许元素为 null。

3、基于链表实现不存在扩容问题。

4、查找时先判断该节点位于前半部分还是后半部分,加快了速度

5、因为基于链表,所以插入删除极快,查找比较慢。

6、实现了栈和队列的相关方法,所以可作为栈,队列,双端队列来用。

作者:coderlmm
链接:https://juejin.im/post/5a90c2fd6fb9a06361089023

LinkedList详解,看这篇就够了相关推荐

  1. Java NIO全面详解(看这篇就够了)

    很多技术框架都使用NIO技术,学习和掌握Java NIO技术对于高性能.高并发网络的应用是非常关键的 NIO简介 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New,是解决高并发 ...

  2. 多线程 详解(掌握这篇就够了)

    文章目录 一.线程简介 Process和Thread 程序 进程 线程 核心概念 二.线程创建(重点) 1.继承Thread 2.实现Runnable接口(推荐使用) 3. 实现Callable接口 ...

  3. Python日志详解【两篇就够了系列】--第二篇loguru

    目录 第二章 Python日志loguru库详解 一.loguru简介 二.日志级别 三.loguru日志常用参数配置解析 1.rotation 2.retention 3.compression 4 ...

  4. Python日志详解【两篇就够了系列】--第一篇logging

    目录 第一章 Python日志模块logging详解 一.logging的框架 1.Logger 2.Handler 3.Formater类 4.Filter类 二.Log级别 三.Log格式 四.常 ...

  5. Socket技术详解(一篇就够了)

    Socket原理 1.什么是Socket 在计算机通信领域,socket 被翻译为"套接字",它是计算机之间进行通信的一种约定或一种方式.通过 socket 这种约定,一台计算机可 ...

  6. Java中Thread详解(一篇就够了)

    前言 操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源.下面简单介绍在操作系统中线程通用实现方式.接下来内容主要对线程模型进行 ...

  7. SPI通信协议详解,一篇就够!

    一.什么是SPI? SPI 的英文全称为 Serial Peripheral Interface,顾名思义为串行外设接口.SPI 是一种同步串行通信接口规范,主要应用于嵌入式系统中的短距离通信.该接口 ...

  8. mybatis标签详解,一篇就够了

    文章目录 前言 一.常用属性 二.SQL定义标签 1. select 2. insert 3. update 4. delete 5. resultMap 6. sql 三.SQL动态标签 1. if ...

  9. 一文详解JavaBean 看这篇就够了

    一文详解JavaBean 看这篇就够了 JavaBean的历史渊源 JavaBean的定义(通俗版) JavaBean应用 < jsp:useBean > < jsp:getProp ...

  10. python java混合编程_详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...

最新文章

  1. 快速排序的递归和非递归实现 c语言版本
  2. integer比较_傻傻分不清?Integer、new Integer() 和 int 的面试题
  3. 在后SCI时代需要什么样的人才?
  4. Kubernetes包管理器Helm发布3.0版本
  5. (转)PostGIS+QGIS+GeoServer+OpenLayers实现数据的存储、服务的发布以及地图的显示...
  6. html跨行使用的属性,HTML表格标记详解4:TD参数中设定跨列跨行属性
  7. http 与https 区别浅析
  8. 测试用ASP.NET建立一个在线RSS新闻聚合器
  9. linux常用命令-文件搜索(locate_find_grep)
  10. App Store2016年最新审核规则
  11. 幸福三月丨盐城北大青鸟女神节快乐!
  12. 【福利倒计时】春风十里不如程序猿的专属福利,拿了这份,2018值了~
  13. c语言软件开发心得:
  14. 分布式系统、集群的时间同步
  15. 如何访问局域网内宿主机上的虚机服务
  16. 动态代理 ---- 框架基础技术
  17. DPDK Release 22.11
  18. 【分布式mysql分库分表中间件sharding】
  19. 自己开发icloud客户端软件
  20. 跟踪上传进度PHP和JavaScript

热门文章

  1. WML元素及其语法格式一览表
  2. STM32 容易烧成电源短路解决方法
  3. STM32L152RC 在keil4中使用printf()和scanf() 函数
  4. Python之pandas,series,可视化
  5. 第六章-template模板
  6. 八、前端开发-JavaScript 客户端存储
  7. MIMIC 以太坊医疗项目开发(3)nodejs安装
  8. 目标和(01背包应用)
  9. python常用指令速查
  10. MTK:oemlock介绍