LinkedList详解,看这篇就够了
文章推荐
- 精选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详解,看这篇就够了相关推荐
- Java NIO全面详解(看这篇就够了)
很多技术框架都使用NIO技术,学习和掌握Java NIO技术对于高性能.高并发网络的应用是非常关键的 NIO简介 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New,是解决高并发 ...
- 多线程 详解(掌握这篇就够了)
文章目录 一.线程简介 Process和Thread 程序 进程 线程 核心概念 二.线程创建(重点) 1.继承Thread 2.实现Runnable接口(推荐使用) 3. 实现Callable接口 ...
- Python日志详解【两篇就够了系列】--第二篇loguru
目录 第二章 Python日志loguru库详解 一.loguru简介 二.日志级别 三.loguru日志常用参数配置解析 1.rotation 2.retention 3.compression 4 ...
- Python日志详解【两篇就够了系列】--第一篇logging
目录 第一章 Python日志模块logging详解 一.logging的框架 1.Logger 2.Handler 3.Formater类 4.Filter类 二.Log级别 三.Log格式 四.常 ...
- Socket技术详解(一篇就够了)
Socket原理 1.什么是Socket 在计算机通信领域,socket 被翻译为"套接字",它是计算机之间进行通信的一种约定或一种方式.通过 socket 这种约定,一台计算机可 ...
- Java中Thread详解(一篇就够了)
前言 操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源.下面简单介绍在操作系统中线程通用实现方式.接下来内容主要对线程模型进行 ...
- SPI通信协议详解,一篇就够!
一.什么是SPI? SPI 的英文全称为 Serial Peripheral Interface,顾名思义为串行外设接口.SPI 是一种同步串行通信接口规范,主要应用于嵌入式系统中的短距离通信.该接口 ...
- mybatis标签详解,一篇就够了
文章目录 前言 一.常用属性 二.SQL定义标签 1. select 2. insert 3. update 4. delete 5. resultMap 6. sql 三.SQL动态标签 1. if ...
- 一文详解JavaBean 看这篇就够了
一文详解JavaBean 看这篇就够了 JavaBean的历史渊源 JavaBean的定义(通俗版) JavaBean应用 < jsp:useBean > < jsp:getProp ...
- python java混合编程_详解java调用python的几种用法(看这篇就够了)
java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...
最新文章
- 快速排序的递归和非递归实现 c语言版本
- integer比较_傻傻分不清?Integer、new Integer() 和 int 的面试题
- 在后SCI时代需要什么样的人才?
- Kubernetes包管理器Helm发布3.0版本
- (转)PostGIS+QGIS+GeoServer+OpenLayers实现数据的存储、服务的发布以及地图的显示...
- html跨行使用的属性,HTML表格标记详解4:TD参数中设定跨列跨行属性
- http 与https 区别浅析
- 测试用ASP.NET建立一个在线RSS新闻聚合器
- linux常用命令-文件搜索(locate_find_grep)
- App Store2016年最新审核规则
- 幸福三月丨盐城北大青鸟女神节快乐!
- 【福利倒计时】春风十里不如程序猿的专属福利,拿了这份,2018值了~
- c语言软件开发心得:
- 分布式系统、集群的时间同步
- 如何访问局域网内宿主机上的虚机服务
- 动态代理 ---- 框架基础技术
- DPDK Release 22.11
- 【分布式mysql分库分表中间件sharding】
- 自己开发icloud客户端软件
- 跟踪上传进度PHP和JavaScript