java8:LinkedList的实现原理

概述

先来看看源码中的这一段注释,我们先尝试从中提取一些信息:

  Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).

  All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.

  Note that this implementation is not synchronized. If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list.

从这段注释中,我们可以得知 LinkedList 是通过一个双向链表来实现的,它允许插入所有元素,包括 null,同时,它是线程不同步的。

如果对双向链表这个数据结构很熟悉的话,学习 LinkedList 就没什么难度了。下面是双向链表的结构:

       双向链表每个结点除了数据域之外,还有一个前指针和后指针,分别指向前驱结点和后继结点(如果有前驱/后继的话)。另外,双向链表还有一个 first 指针,指向头节点,和 last 指针,指向尾节点。

继承关系

image.png

属性

接下来看一下 LinkedList 中的属性:

  //链表的节点个数
  transient int size = 0;
  //指向头节点的指针
  transient Node<E> first;
  //指向尾节点的指针
  transient Node<E> last;

LinkedList 的属性非常少,就只有这些。通过这三个属性,其实我们大概也可以猜测出它是怎么实现的了。

方法

结点结构

    Node 是在 LinkedList 里定义的一个静态内部类,它表示链表每个节点的结构,包括一个数据域 item,一个后置指针 next,一个前置指针 prev。
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;
        }
    }

添加元素

    对于链表这种数据结构来说,添加元素的操作无非就是在表头/表尾插入元素,又或者在指定位置插入元素。因为 LinkedList 有头指针和尾指针,所以在表头或表尾进行插入元素只需要 O(1) 的时间,而在指定位置插入元素则需要先遍历一下链表, 所以复杂度为 O(n)。

在表头添加元素的过程如下:

    当向表头插入一个节点时,很显然当前节点的前驱一定为 null,而后继结点是 first指针指向的节点,当然还要修改  first  指针指向新的头节点。除此之外,原来的头节点变成了第二个节点,所以还要修改原来头节点的前驱指针,使它指向表头节点, 源码的实现如下:

    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        //当前节点的前驱指向null,后继指针原来的头节点
        final Node<E> newNode = new Node<>(null, e, f);
        //头指针指向新的头节点
        first = newNode;
        //如果原来有头节点,则更新原来节点的前驱指针,否则更新尾指针
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

在表尾添加元素跟在表头添加元素大同小异,如图所示:

   当向表尾插入一个节点时,很显然当前节点的后继一定为 null,而前驱结点是 last 指针指向的节点,然后还要修改 last 指针指向新的尾节点。此外,还要修改原来尾节点的后继指针,使它指向新的尾节点,源码的实现如下:
  /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        //当前节点的前驱指向尾节点,后继指向null
        final Node<E> newNode = new Node<>(l, e, null);
        /尾指针指向新的尾节点
        last = newNode;
        //如果原来有尾节点,则更新原来节点的后继指针,否则更新头指针
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

最后,在指定节点之前插入,如图所示:

当向指定节点之前插入一个节点时,当前节点的后继为指定节点,而前驱结点为指定节点的前驱节点。此外,还要修改前驱节点的后继为当前节点,以及后继节点的前驱为当前节点,源码的实现如下:

    /**
     * Inserts element e before non-null Node succ.
     */
    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++;
    }

删除元素

  删除操作与添加操作大同小异,例如删除指定节点的过程如下图所示,需要把当前节点的前驱节点的后继修改为当前节点的后继,以及当前节点的后继结点的前驱修改为当前节点的前驱(是不是很绕?):

删除头节点和尾节点跟删除指定节点非常类似,就不一一介绍了,源码如下:

    /**
     * Unlinks non-null first node 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 = next;
        if (next == null)
            last = null;
        else
            //新头节点的前驱为null
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null last node 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 = prev;
        if (prev == null)
            first = null;
        else
            //新尾节点的后继为null
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null node x.
     */
    //删除指定节点,返回指定元素的值
    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;
    }

获取元素

获取元素的方法一看就懂,我就不必多加解释了。

  /**
     * Returns the first element in this list.
     *
     * @return the first element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    /**
     * Returns the last element in this list.
     *
     * @return the last element in this list
     * @throws NoSuchElementException if this list is empty
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

    /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    //获取指定下标的元素 
    public E get(int index) {
        //检查下标是否超过元素个数
        checkElementIndex(index);
        //遍历链表取值,根据下标是否超过链表长度的一半,来选择从头部开始遍历还是从尾部开始遍历
        return node(index).item;
    }
    private void checkElementIndex(int index) {
            if (!isElementIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    /**
     * Tells if the argument is the index of an existing element.
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
   /**
     * Returns the (non-null) Node at the specified element 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;
        }
    }

常用方法

    前面介绍了链表的添加和删除操作,你会发现那些方法都不是 public 的,LinkedList 是在这些基础的方法进行操作的,下面就来看看我们可以调用的方法有哪些。

removeFirst:删除表头元素

/**
     * Removes and returns the first element from this list.
     *
     * @return the first element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

removeLast:删除表尾元素

 /**
     * Removes and returns the last element from this list.
     *
     * @return the last element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

addFirst:插入新的表头节点

/**
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }

addLast:插入新的表尾节点

/**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     */
    public void addLast(E e) {
        linkLast(e);
    }

size:链表的大小

/**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }

add:添加元素到表尾

 /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

remove:删除指定元素

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    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;
    }

get:获取指定下标的元素

 /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

set:替换指定下标的值

 /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

add:在指定位置插入节点

 /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

remove:删除指定下标的节点

 /**
     * Removes the element at the specified position in this list.  Shifts any
     * subsequent elements to the left (subtracts one from their indices).
     * Returns the element that was removed from the list.
     *
     * @param index the index of the element to be removed
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

peek:获取表头节点的值,表头为空返回 null

/**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

element:获取表头节点的值,表头为空抛出异常

/**
     * Retrieves, but does not remove, the head (first element) of this list.
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E element() {
        return getFirst();
    }

poll:获取表头节点的值,并删除表头节点,表头为空返回 null

/**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list, or {@code null} if this list is empty
     * @since 1.5
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

push:添加元素到表头

 /**
     * Pushes an element onto the stack represented by this list.  In other
     * words, inserts the element at the front of this list.
     *
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @since 1.6
     */
    public void push(E e) {
        addFirst(e);
    }

pop:删除表头元素

 /**
     * Pops an element from the stack represented by this list.  In other
     * words, removes and returns the first element of this list.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this list (which is the top
     *         of the stack represented by this list)
     * @throws NoSuchElementException if this list is empty
     * @since 1.6
     */
    public E pop() {
        return removeFirst();
    }

总结

1、LinkedList 的底层结构是一个带头/尾指针的双向链表,可以快速的对头/尾节点进行操作。
2、相比数组,链表的特点就是在指定位置插入和删除元素的效率较高,但是查找的效率就不如数组那么高了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/466080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu下摩尔线程s80配置ai绘图环境

首先我的桌面是gdm,然后安装github上的sdk&#xff0c;重启进不去桌面了 解决方法&#xff1a; 开机以后选ubuntu的高级选项&#xff0c;换旧一点的linux内核&#xff0c;然后卡在进程上&#xff0c;ctrlaltf2斤tty sudo apt remove musa 卸载完驱动就可以进系统了

Java SE 认识异常 (Java SE完结篇)

1. 异常的概念与体系结构 1.1 异常的概念 在我们的生活中,一个人如果表情痛苦,我们可能会问: 你是生病了吗? 需要我陪你去看医生吗? 程序也和人是一样的,均会发生一些"生病"的行为,比如: 数据格式不对, 数组越界,网络中断等, 我们把这种程序出现的"生病&qu…

ASO优化:App在App Store的权重影响因素

1.App的标题描述 App的标题、描述是能引导用户下载的重要部分&#xff0c;此处关键词占比的权重是最大的。比如说爱奇艺&#xff0c;最近主推的就是由任嘉伦、刑菲主演的《烈焰》。它就把主推的内容放在副标题处&#xff0c;获得很大的曝光量&#xff0c;娱乐榜直接排第一名了…

C语言学习笔记day8

一维数组冒泡排序法 1. 作用 将乱序的一维数组按照从小到大的顺序排列 2. 原理示意图 3. 代码 #include <stdio.h> #include <stdlib.h> #include <time.h>int main(void) {int a[5] {0};int len sizeof(a) / sizeof(a[0]);int i 0;int j 0;int tmp …

Vue工程化基础

一Ajax 1.1Ajax概述&#xff1a; 异步与同步 繁琐被淘汰了。 二Axios2 前后端混合开发&#xff1a; 前后端分离开发&#xff1a; YAPI 三前端开发工程化 四Vue脚手架 项目的认识 改变端口号 五Vue开发流程&#xff1a; 六Element组件 6.1快速入门 下载> npm install e…

Python数据分析-Matplotlib1

一、折线图的绘制 1.数据分析流程 2.运用Matplot绘制折线图 #encodingutf-8 import random from matplotlib import pyplot as plt #绘图工具库 from matplotlib import font_manager #解决中文显示问题 from cProfile import label #设置字体方式 my_font font_manager.Fon…

kafka集群介绍及搭建

介绍 kafka是一个高性能、低延迟、分布式的消息传递系统&#xff0c;特点在于实时处理数据。集群由多个成员节点broker组成&#xff0c;每个节点都可以独立处理消息传递和存储任务。 路由策略 发布消息由key、value组成&#xff0c;真正的消息是value&#xff0c;key是标识路…

Two Birds with One Stone

learnable mask M 辅助信息 作者未提供代码

Illustrator 2024:创意与技术的完美融合,引领矢量设计新潮流

Illustrator 2024是一款由Adobe公司倾力打造的强大矢量图形设计软件&#xff0c;以其丰富的绘图工具、卓越的设计功能和直观的操作界面&#xff0c;成为专业设计师和创意工作者的首选工具。这款软件不仅提供了画笔、铅笔、形状、路径等多种工具&#xff0c;帮助用户轻松创建各种…

Python+Appium+Pytest+Allure实战APP自动化测试!

pytest只是单独的一个单元测试框架&#xff0c;要完成app测试自动化需要把pytest和appium进行整合&#xff0c;同时利用allure完成测试报告的产出。 编写常规的线性脚本具体的步骤如下&#xff1a; 1、设计待测试APP的自动化测试用例 2、新建app测试项目 3、配置conftest.py文…

精读《架构设计之 DCI》

本期精读文章是&#xff1a;The DCI Architecture 1 引言 随着前端 ES6 ES7 的一路前行&#xff0c; 我们大前端借鉴和引进了各种其他编程语言中的概念、特性、模式; 我们可以使用函数式 Functional 编程设计&#xff0c;可以使用面向对象 OOP 的设计&#xff0c;可以使用面向…

ai写作一键生成,分享6种好用的写作软件,一定要看

在写文章时&#xff0c;我们常常会遇到灵感丧失、词句不顺的情况&#xff0c;为了解决这一问题&#xff0c;小编为大家推荐几款实用的AI写作软件&#xff0c;一同来探索一下吧&#xff01; 一、爱制作AI 爱制作AI是一款专注于写作的软件&#xff0c;强大的智能数据库让它备受…

避免内存泄漏及泄漏后的排查方法【C++】

内存泄漏 前言编码std::unique_ptr申请单个对象申请对象数组 std::shared_ptr申请单个对象申请对象数组 编码总结 前言 最近在工作中被内存泄漏疯狂折磨&#xff0c;整理一下自己的思考。 编码 最近在工作中被内存泄漏疯狂折磨&#xff0c;我真的奉劝各位&#xff0c;如果你…

生成式 AI 术语指南:带有配图说明,没有数学公式

编者按&#xff1a; 生成式人工智能技术的发展日新月异&#xff0c;这一领域涉及到了越来越多的专业术语和概念。对于刚接触这一领域的新手来说&#xff0c;理解这些术语算是一个门槛。我们有必要整理和解释这些术语&#xff0c;帮助更多人快速入门&#xff0c;投身 AI 事业。 …

Leetcode 202.快乐数 JAVA

题目 思路 要注意题目中说的无限循环&#xff1a;它是指在求平方和的过程中&#xff0c;会再次出现之前的值&#xff08;想象一个圈&#xff09;&#xff0c;这种情况的时候肯定算不出1来。 所以我们要设定跳出循环的条件是&#xff1a;当平方和结果为1或者出现循环了 出现循…

应届生岗位直达服务

详情请私信了解 技术面试&#xff1a; C技术深入学习资源礼包&#xff08;岗位技术栈查漏补缺/非卖品&#xff09; 系统设计面试的准备 模拟技术面试和问题纠错反馈 职业发展和软技能&#xff1a; 简历优化和面试技巧 职业规划和目标设定 沟通和团队协作技能 实际…

Redis的安装和部署教程(Windows环境)

一、安装Redis服务 1、下载Redis压缩包 以下这个是我网盘里面的&#xff08;这个是v8.0版本的&#xff0c;支持导入.rdb数据文件&#xff09; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;x0f1 --来自百度网盘超级会员V5的分享 2、解压到文件夹 将下载的压缩…

NSGA-III算法:如何在多目标优化问题中找到最合适的解

当我们面临多个目标函数时&#xff0c;单目标的遗传算法可能无法满足需求。这时&#xff0c;我们可以引入多目标遗传算法。在这种情况下&#xff0c;目标函数可能存在冲突&#xff0c;例如&#xff0c;一个目标函数需要最小化&#xff0c;而另一个目标函数需要最大化。某个目标…

利用Python进行网络爬虫:Beautiful Soup和Requests的应用【第131篇—Beautiful Soup】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 利用Python进行网络爬虫&#xff1a;Beautiful Soup和Requests的应用 在网络数据变得日益丰…

基于多尺度视网膜增强图像去雾算法(MSR,Multi-Scale Retinex),Matalb实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供有偿…