一文搞懂LinkedList

01 原理

LinkedList底层采用双向链表实现。与ArrayList不同,链表不需要扩容,除此之外还会有以下特点。

02 特点

  1. 非连续的内存,因此不支持随机访问,只能通过节点持有的指针,依次向后(向前)查找就安排,查找的复杂度高。

image

2. 插入操作性能好。只需要插入位置的前后节点的引用指向该节点即可。

image

  3.  删除性能好,与插入类似,将删除节点前后节点的引用互相指向即可。

image

03 源码

最后从源码里具体分析一下,LinkedList中的添加(add),查找(get),删除(remove),插入(add)

添加(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); // 创建e的节点,其中prev指针指向尾部节点,next为空
        last = newNode; // 将尾节点修改为添加的节点
        if (l == null)
            first = newNode; // 没有尾节点,则该节点也是头节点
        else
            l.next = newNode; // 旧的尾节点 next指针指向新添加的节点
        size++; // 数据大小 + 1
        modCount++;
    }

查找(get):

public E get(int index) {
        checkElementIndex(index); 
        return node(index).item; // 查找
    }
 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;
        }
    }

插入(add):

public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);  // 添加节点到链表末尾,与添加逻辑一致
        else
            linkBefore(element, node(index)); // 查找下标指向的节点,插入新节点
    }

void linkBefore(E e, Node<E> succ) { // e:插入节点,succ:插入位置上的节点
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ); // 创建插入e的节点,其中prev指针指向succ的前继节    
                                                                                 //  点,next指向succ节点
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode; // succ前继节点的next指针指向插入的节点
        size++;
        modCount++;
    }

删除(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; // 前继节点的next指针指向后继节点
            x.prev = null; // 删除节点的pre指针置为null
        }

        if (next == null) { // 插入位置为尾部
            last = prev;
        } else {
            next.prev = prev; // 后继节点的prev指针指向前继节点
            x.next = null; // 删除节点的next指针置为null
        }

        x.item = null; // 删除节点的指置为null,让GC可以回收
        size--;
        modCount++;
        return element;
    }

java developer.

110 声望
2 粉丝
0 条评论
推荐阅读
springmvc详解(3)请求分发流程之拦截器 HandlerInterceptor
当Servlet接收到请求后会最终调用doDispatch方法后会去找到对应的HandlerMapping,同时也会找到配置的拦截器,最终组成需要的HandlerExecutionChain执行链(这里省略部分代码保留主要功能):

小燃儿阅读 1.5k

刨根问底 Redis, 面试过程真好使
充满寒气的互联网如何在面试中脱颖而出,平时积累很重要,八股文更不能少!下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把🔥。

菜农曰17阅读 957

封面图
PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 2k评论 2

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide5阅读 827

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬8阅读 1.1k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.9k

封面图
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图

java developer.

110 声望
2 粉丝
宣传栏