Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • MacOS
  • Linux
  • Windows
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • MacOS
  • Linux
  • Windows
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
    • 异常处理
  • Java集合

    • Java集合概述
    • ArrayList
    • LinkedList
      • 1. LinkedList 的基本特性
      • 2. 构造函数
      • 3. 添加元素 (add() 方法)
      • 4. 获取元素 (get() 方法)
      • 5. 删除元素 (remove() 方法)
      • 6. LinkedList 实现 Deque 的方法特性
        • 6.1 添加元素到头部 (addFirst() 方法)
        • 6.2 添加元素到尾部 (addLast() 方法)
        • 6.3 删除头部元素 (removeFirst() 方法)
        • 6.4 删除尾部元素 (removeLast() 方法)
        • 6.5 获取头部和尾部元素 (getFirst() 和 getLast() 方法)
      • 7. 与 ArrayList 的比较
      • 8. 总结
    • HashMap
    • LinkedHashMap
    • HashSet
    • TreeMap
    • Queue&Deque
  • Java并发

    • Java并发概述
    • 线程与进程
    • Thread类与线程生命周期
    • 线程安全
    • synchronized关键字
    • volatile关键字
    • Java内存模型(JMM)
    • 线程间通信
    • 线程池
    • 并发工具类
    • 原子操作类Atomic
    • 并发锁
    • 并发容器
    • ConcurrentHashMap
    • BlockingQueue
    • CopyOnWriteArrayList
    • ThreadLocal
    • Fork/Join框架
    • ScheduledThreadPoolExecutor
    • CompletableFuture
  • Java IO

    • Java IO概述
  • JVM

    • JVM概述
目录

LinkedList

LinkedList 是 Java 集合框架中的另一个常用类,它实现了 List、Deque 和 Queue 接口,基于双向链表的结构,可以高效地进行插入和删除操作。

# 1. LinkedList 的基本特性

  • 底层结构:LinkedList 基于双向链表实现,每个节点包含对前后节点的引用。
  • 高效的插入和删除:由于链表结构的特点,在链表头部和尾部的插入、删除操作时间复杂度为 O(1),在中间位置插入或删除操作需要遍历链表,时间复杂度为 O(n)。
  • 随机访问性能差:由于链表的线性结构,随机访问一个元素的时间复杂度为 O(n)。
  • 线程不安全:LinkedList 不是线程安全的,如果需要在多线程环境中使用,需要进行额外的同步处理。

# 2. 构造函数

LinkedList 提供了两个主要的构造函数:

  • 默认构造函数:创建一个空链表。
    public LinkedList() {
        first = last = null;
    }
    
  • 集合构造函数:根据传入的集合创建链表。
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    

# 3. 添加元素 (add() 方法)

LinkedList 的 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;
    else
        l.next = newNode;
    size++;
    modCount++;
}
  • linkLast():创建一个新节点,将其链接到链表的尾部。
    • 如果链表为空(last == null),新节点既是头节点也是尾节点。
    • 否则,将新节点作为当前尾节点的后继节点。

LinkedList 的 addll() 方法用于批量添加元素,核心逻辑如下:

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;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}
  • addAll(int index, Collection<? extends E> c) 方法细节如下:
    • checkPositionIndex(index):检查索引是否在有效范围内。
    • toArray():将集合转换为数组,方便后续的批量插入操作。
    • 插入逻辑:根据索引找到插入位置,将新元素逐一插入链表中。
    • 调整前后节点的引用:对于插入位置的前驱和后继节点,调整其引用以保持链表的完整性。
    • 更新链表大小和修改计数:增加链表的 size,并更新 modCount。

# 4. 获取元素 (get() 方法)

LinkedList 的 get() 方法用于通过索引获取元素,时间复杂度为 O(n):

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

Node<E> node(int 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() 方法:根据索引在链表中找到对应的节点。
    • 如果索引位于前半部分,从头节点开始遍历。
    • 如果索引位于后半部分,从尾节点开始遍历。

# 5. 删除元素 (remove() 方法)

LinkedList 的 remove() 方法有两种重载方式,一种是通过索引删除元素,另一种是通过对象删除元素。

  • 通过索引删除元素:
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    
    E unlink(Node<E> x) {
        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() 方法:断开要删除节点与链表的连接,并调整前后节点的引用。

# 6. LinkedList 实现 Deque 的方法特性

LinkedList 还实现了 Deque 接口,使其可以用作双端队列,支持在两端插入和删除元素。以下是 LinkedList 实现 Deque 接口的一些方法及其源码分析:

# 6.1 添加元素到头部 (addFirst() 方法)

addFirst() 方法用于在链表头部添加元素:

public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
  • linkFirst():创建一个新节点,将其作为链表的头节点。
    • 如果链表为空,新节点既是头节点也是尾节点。
    • 否则,将新节点作为当前头节点的前驱节点。

# 6.2 添加元素到尾部 (addLast() 方法)

addLast() 方法用于在链表尾部添加元素,与 add() 方法相同:

public void addLast(E e) {
    linkLast(e);
}
  • addLast() 方法直接调用 linkLast() 方法,将元素添加到链表的尾部。

# 6.3 删除头部元素 (removeFirst() 方法)

removeFirst() 方法用于删除链表头部的元素:

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // 帮助 GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}
  • unlinkFirst():断开头节点与链表的连接,并调整后继节点的引用。
    • 如果链表只有一个节点,删除后头节点和尾节点都设置为 null。

# 6.4 删除尾部元素 (removeLast() 方法)

removeLast() 方法用于删除链表尾部的元素:

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // 帮助 GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}
  • unlinkLast():断开尾节点与链表的连接,并调整前驱节点的引用。
    • 如果链表只有一个节点,删除后头节点和尾节点都设置为 null。

# 6.5 获取头部和尾部元素 (getFirst() 和 getLast() 方法)

  • getFirst() 方法:

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    
    • 获取头节点的元素,如果链表为空则抛出 NoSuchElementException。
  • getLast() 方法:

    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
    
    • 获取尾节点的元素,如果链表为空则抛出 NoSuchElementException。

# 7. 与 ArrayList 的比较

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问性能 O(1),查询性能优越 O(n),需要遍历节点
插入和删除性能 尾部插入性能高;中间插入 O(n) 头尾插入性能高;中间插入 O(n)
内存占用 连续内存块,内存利用率高 每个节点有前后引用,内存占用较大
扩容机制 初始容量 10,每次扩容增至 1.5 倍 无需扩容,动态调整
线程安全 线程不安全 线程不安全
  • ArrayList 适用场景:适合频繁随机访问元素、插入和删除操作较少的场景,例如读取数据比较多、写入相对少的场景。
  • LinkedList 适用场景:适合频繁插入和删除元素的场景,例如在头尾插入或删除元素的队列实现。

# 8. 总结

LinkedList 通过实现 Deque 接口,使其可以作为双端队列使用,支持在头尾两端进行高效的插入和删除操作。这些方法的实现利用了链表的双向引用特性,使得在头部和尾部的操作时间复杂度均为 O(1)。在需要频繁在两端进行操作的场景中,LinkedList 是非常合适的选择。

上次更新: 2024/10/31, 18:28:18
ArrayList
HashMap

← ArrayList HashMap→

Copyright © 2023-2024 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式