Java:LinkedList类小记

对 Java 中的 LinkedList类,做一个微不足道的小小小小记

概述

java.util.LinkedList 集合数据存储的结构是循环双向链表结构。方便元素添加、删除的集合。

循环双向链表:

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

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

可以画出如下示意图:

就是这样的结构,是的链表可以作为队列/双端队列使用,在刷题的时候无敌

成员属性

LinkedList 继承了 AbstractSequentialList 类,实现了 List、Deque接口

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
// 双向链表的头结点
transient Node<E> first;
// 双向链表的尾节点
transient Node<E> last; // 静态内部类
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;
}
}
}

常用方法

构造方法

// 默认/无参构造
public LinkedList() {
} // 根据其他的集合类构造
public LinkedList(Collection<? extends E> c) {
this(); // 调用了上面的构造
addAll(c);
}

添加元素

add(E e)在链表末尾插入元素:

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;
// 当之前的末尾节点为null,说明其实是个空链表
if (l == null)
// 链表头和尾现在都是这个newnode
first = newNode;
else
// 链表不为空,之前的last.next直线新的last
l.next = newNode;
size++;
modCount++;
}

add(int index, E element)在指定位置插入元素

public void add(int index, E element) {
// 1.判断index是否合法
checkPositionIndex(index); if (index == size)
// 2.当index就是size,则在末尾插入,就是上面的方法
linkLast(element);
else
// 3.在链表的指定位置插入元素
// 3.1 node
// 3.2 linkBefore
linkBefore(element, node(index));
} // 1.1 判断index是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 1.2上述的isPositionIndex方法:
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
} // 3.1 根据index找到对于的node位置
// 比较需要插入的 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;
}
} // 3.1 在链表的指定位置插入元素
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;
else
pred.next = newNode;
size++;
modCount++;
}

移除元素

remove(Object o)移除指定元素:

public boolean remove(Object o) {
if (o == null) {
// 当o为null时,移除第一个为null的元素
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 当o不为null时,移除相应的元素
for (Node<E> x = first; x != null; x = x.next) {
// 这里的通过equals的方法进行判断
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
} // 上述函数中的unlink函数
// 主要的逻辑就是:删除某节点,并将该节点的上一个节点(如果有)和下一个节点(如果有)关联起来
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;
}

remove(int index)移除指定索引的元素:

public E remove(int index) {
// 判断index是否合法
checkElementIndex(index);
// 修改节点的连接
return unlink(node(index));
}

其他添加/删除方法:

和队列相关的一些方法:

public boolean offer(E e):在 list 末尾添加上元素;

public boolean offerFirst(E e):在 list 头添加元素;

public boolean offerLast(E e):在 list 末尾添加上元素,本质上和 offer 方法是一样的;

public E poll():出 list 头的元素,并返回;

public E pollFirst():出 list 头的元素,并返回;

public E pollLast():出 list 末尾的元素,并返回;

public E peek():返回 list 头部的元素;

public E peekFirst():返回 list 头部的元素;

public E peekLast():返回 list 尾部的元素;

和栈相关的一些方法:

public void push(E e):栈的push操作

public E pop():栈的pop操作

ArrayList & LinkedList

这部分内容在Java:ArrayList类小记中已经贴了,这里再贴一遍

虽然还没看过 LinkedList 的源码,这里就先贴一个 ArrayList & LinkedList 的区别

ArrayList:底层是基于动态数组实现的,查找快,增删较慢

LinkedList:底层是基于链表实现的。确切的说是循环双向链表(JDK1.6 之前是双向循环链表、JDK1.7 之后取消了循环),查找慢、增删快。LinkedList 链表由一系列表项连接而成,一个表项包含 3 个部分:元素内容、前驱表和后驱表。链表内部有一个 header 表项,既是链表的开始也是链表的结尾。header 的后继表项是链表中的第一个元素,header 的前驱表项是链表中的最后一个元素。

补充:

ArrayList 的增删未必就是比 LinkedList 要慢:

  1. 如果增删都是在末尾来操作(每次调用的都是 remove()add()),此时 ArrayList 就不需要移动和复制数组来进行操作了。如果数据量有百万级的时,速度是会比 LinkedList 要快的。

  2. 如果删除操作的位置是在中间。由于 LinkedList 的消耗主要是在遍历上( LinkedList 会比较查询的index与链表长度,选择从头还是从尾开始查找),ArrayList 的消耗主要是在移动和复制上(底层调用的是 arrayCopy() 方法,是 native 方法,native的方法还是很快的)。LinkedList 的遍历速度是要慢于 ArrayList 的复制移动速度的,如果数据量有百万级的时,还是 ArrayList 要快。

    // ArrayList
    public void add(int index, E element) {
    rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
    // 这个是一个native的方法
    System.arraycopy(elementData, index, elementData, index + 1,
    size - index);
    elementData[index] = element;
    size++;
    } // 比较需要插入的 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;
    }
    }

参考:https://www.cnblogs.com/syp172654682/p/9817277.html

Java:LinkedList类小记的更多相关文章

  1. java LinkedList(链表)

    LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问方面却要逊色一些 LinkedList ...

  2. Java:ArrayList类小记

    Java:ArrayList类小记 对 Java 中的 ArrayList类,做一个微不足道的小小小小记 概述 java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素. ...

  3. 【Java源码分析】LinkedList类

    LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...

  4. JDK1.8源码(六)——java.util.LinkedList 类

    上一篇博客我们介绍了List集合的一种典型实现 ArrayList,我们知道 ArrayList 是由数组构成的,本篇博客我们介绍 List 集合的另一种典型实现 LinkedList,这是一个有链表 ...

  5. 5.1 java类集(java学习笔记)Collection、List接口及ArrayList、LinkedList类。

    一.类集 类集就是一组动态的对象数组,说类集可能不好理解,类集又称容器,容器顾名思义就是放东西的地方. 类集就是为了让我们更加简洁,方便的存放.修改.使用数据的. 二.Collection接口 我们看 ...

  6. JAVA基础知识(二):List接口、ArrayList类和LinkedList类

    List接口继承了Collection接口,位于java.util包中.它包含Collection接口的所有方法,外加其他一些方法(具体实现参考源码),比较重要的有: anyType get(int ...

  7. Java:ConcurrentHashMap类小记-3(JDK8)

    Java:ConcurrentHashMap类小记-3(JDK8) 结构说明 // 所有数据都存在table中, 只有当第一次插入时才会被加载,扩容时总是以2的倍数进行 transient volat ...

  8. Java:ConcurrentHashMap类小记-2(JDK7)

    Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...

  9. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

随机推荐

  1. 【算法】使用Golang实现加权负载均衡算法

    背景描述 如下图所示,负载均衡做为反向代理,将请求方的请求转发至后端的服务节点,实现服务的请求. 在nginx中可以通过upstream配置server时,设置weight表示对应server的权重. ...

  2. Dockerfile优化——supervisor服务

    一.理解supervisor(supervisor服务不仅在容器中可用,在宿主机中也适用) 1.Dockerfile中的CMD可以指定启动容器后执行的第一个命令,但是当有多个服务进程需要启动的时候,就 ...

  3. Linux--MySQL 日志管理、备份与恢复

    MySQL 日志管理.备份与恢复一.MySQL 日志管理二.数据库备份的重要性与分类  1.数据备份的重要性  2.从物理与逻辑的角度,备份分为  3.从数据库的备份策略角度,备份可分为三.常见的备份 ...

  4. Asp.net MVC Vue Axios无刷新请求数据和响应数据

    Model层Region.cs using System; using System.Collections.Generic; using System.Linq; using System.Web; ...

  5. Fastjson 1.2.22-24 反序列化漏洞分析(2)

    Fastjson 1.2.22-24 反序列化漏洞分析(2) 1.环境搭建 我们以ubuntu作为被攻击的服务器,本机电脑作为攻击者 本机地址:192.168.202.1 ubuntu地址:192.1 ...

  6. clickonce的密钥到期问题处理

    最近clickonce的密钥到期了,在网上找了些文章用来修改密钥的到期时间,已成功生成新密钥,好不好使暂时未测. 在此小结一下,以备参考: 1.在原密钥所属电脑上cmd执行如下命令 renewcert ...

  7. WPF listbox中Checkbox横向排列

    <ListBox Height="220" Margin="0" ItemsSource="{Binding RightCollection}& ...

  8. 关于软链接ln -s 的使用

    1.效果跟windows创建快捷方式是一样的,先找到要被创建的原始文件或目录.然后才能创建. 2.格式:ln  -s   [源文件或目录]   [目标文件或目录] 3.源文件或目录必须是绝对目录. 4 ...

  9. 注释swap分区

    grep  "#" fstab |grep "swap" >/dev/null || sed -i 's/^.*swap/#&/g' fstab

  10. 概述 .NET 6 ThreadPool 实现

    目录 前言 任务的调度 基本调度单元 IThreadPoolWorkItem 实现类的实例. Task 全局队列 本地队列 偷窃机制 Worker Thread 的生命周期管理 线程生命注入实验 .N ...