Java集合:LinkedList详解

简介: 本文就LinkedList的几个主要方法展开介绍,并结合几个图片来介绍几个重要操作。

概述


本文就LinkedList的几个主要方法展开介绍,并结合几个图片来介绍几个重要操作。

 

基础属性


transient int size = 0; // 节点数量
/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;    // 第一个节点(头结点)
/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last; // 最后一个节点(尾节点)
//...
private static class Node<E> {  // Node的数据结构
    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;
    }
}


基本数据结构图如下:

image.png

add方法


public boolean add(E e) {
    linkLast(e);    // 调用linkLast方法, 将节点添加到尾部
    return true;
}
public void add(int index, E element) { // 在index位置插入节点,节点值为element
    checkPositionIndex(index);
    if (index == size)  // 如果索引为size,即将element插入链表尾部
        linkLast(element);
    else    // 否则,将element插入原index位置节点的前面,即:将element插入index位置,将原index位置节点移到index+1的位置
        linkBefore(element, node(index));   // 将element插入index位置
}


add(E e):调用linkLast方法将元素添加到尾部(linkLast方法详解见下文)

add(int index, E element):


  1. 检查index是否越界
  2. 比较index与size,如果index==size,则代表插入位置为链表尾部,调用linkLast方法(linkLast方法详解见下文),否则调用linkBefore方法(LinkBefore方法详解见下文)


get方法


public E get(int index) {   
    checkElementIndex(index);   // 校验index是否越界
    return node(index).item;    // 根据index, 调用node方法寻找目标节点,返回目标节点的item
}
  1. 校验index是否越界
  2. 调用node方法寻找目标节点,并返回目标节点的item(node方法详解见下文)

set方法


public E set(int index, E element) {    // 替换index位置节点的值为element
    checkElementIndex(index);   // 检查index是否越界
    Node<E> x = node(index);    // 根据index, 调用node方法寻找到目标节点
    E oldVal = x.item;  // 节点的原值
    x.item = element;   // 将节点的item属性设为element
    return oldVal;  //返回节点原值
}


  1. 检查index是否越界
  2. 调用node方法寻找目标节点(node方法详解见下文)
  3. 将目标节点的item属性设为element


remove方法


public boolean remove(Object o) {
    if (o == null) {    // 如果o为空, 则遍历链表寻找item属性为空的节点, 并调用unlink方法将该节点移除
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {    // 如果o不为空, 则遍历链表寻找item属性跟o相同的节点, 并调用unlink方法将该节点移除
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}
public E remove(int index) {    // 移除index位置的节点
    checkElementIndex(index);   // 检查index是否越界
    return unlink(node(index)); // 移除index位置的节点
}


remove(Object o):


  1. 判断o是否为null,如果o为null,则遍历链表寻找item属性为空的节点,并调用unlink方法将该节点移除(unlink方法详解见下文)
  2. 如果o不为null, 则遍历链表寻找item属性跟o相同的节点,并调用unlink方法将该节点移除(unlink方法详解见下文)


remove(int index):


  1. 检查index是否越界
  2. 调用unlink方法,移除index位置的节点(unlink方法详解见下文)


clear方法


public void clear() {   // 清除链表的所有节点
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    for (Node<E> x = first; x != null; ) {  // 从头结点开始遍历将所有节点的属性清空
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;    // 将头结点和尾节点设为null
    size = 0;
    modCount++;
}


  1. 从first节点开始,遍历将所有节点的属性清空
  2. 将first节点和last节点设为null


linkLast方法


void linkLast(E e) {    // 将e放到链表的最后一个节点
    final Node<E> l = last; // 拿到当前的尾节点l节点
    final Node<E> newNode = new Node<>(l, e, null); // 使用e创建一个新的节点newNode, prev属性为l节点, next属性为null
    last = newNode; // 将当前尾节点设置为上面新创建的节点newNode
    if (l == null)  // 如果l节点为空则代表当前链表为空, 将newNode设置为头结点
        first = newNode;
    else    // 否则将l节点的next属性设置为newNode
        l.next = newNode;
    size++;
    modCount++;
}
  1. 拿到当前的尾节点l节点
  2. 使用e创建一个新的节点newNode,prev属性为l节点,next属性为null
  3. 将当前尾节点设置为上面新创建的节点newNode
  4. 如果l节点为空则代表当前链表为空, 将newNode设置为头结点,否则将l节点的next属性设置为newNode


过程如图所示


image.png

linkBefore方法


void linkBefore(E e, Node<E> succ) {    // 将e插入succ节点前面
    // assert succ != null;
    final Node<E> pred = succ.prev; // 拿到succ节点的prev节点
    final Node<E> newNode = new Node<>(pred, e, succ);  // 使用e创建一个新的节点newNode,其中prev属性为pred节点,next属性为succ节点
    succ.prev = newNode;    // 将succ节点的prev属性设置为newNode
    if (pred == null)   // 如果pred节点为null,则代表succ节点为头结点,要把e插入succ前面,因此将first设置为newNode
        first = newNode;
    else    // 否则将pred节点的next属性设为newNode
        pred.next = newNode;
    size++;
    modCount++;
}


  1. 拿到succ节点的prev节点
  2. 使用e创建一个新的节点newNode,其中prev属性为pred节点,next属性为succ节点
  3. 将succ节点的prev属性设置为newNode
  4. 如果pred节点为null,则代表succ节点为头结点,要把e插入succ前面,因此将first设置为newNode,否则将pred节点的next属性设为newNode


过程如图所示


image.png


unlink方法


E unlink(Node<E> x) {   // 移除链表上的x节点
    // assert x != null;
    final E element = x.item;   // x节点的值
    final Node<E> next = x.next;    // x节点的下一个节点
    final Node<E> prev = x.prev;    // x节点的上一个节点
    if (prev == null) { // 如果prev为空,则代表x节点为头结点,则将first指向next即可
        first = next;
    } else {    // 否则,x节点不为头结点,
        prev.next = next;   // 将prev节点的next属性指向x节点的next属性
        x.prev = null;  // 将x的prev属性清空
    }
    if (next == null) { // 如果next为空,则代表x节点为尾节点,则将last指向prev即可
        last = prev;
    } else {    // 否则,x节点不为尾节点
        next.prev = prev;   // 将next节点的prev属性指向x节点的prev属性
        x.next = null;  // 将x的next属性清空
    }
    x.item = null;  // 将x的值清空,以便垃圾收集器回收x对象
    size--;
    modCount++;
    return element;
}


  1. 定义element为x节点的值,next为x节点的下一个节点,prev为x节点的上一个节点
  2. 如果prev为空,则代表x节点为头结点,则将first指向next即可;否则,x节点不为头结点,将prev节点的next属性指向x节点的next属性,并将x的prev属性清空
  3. 如果next为空,则代表x节点为尾节点,则将last指向prev即可;否则,x节点不为尾节点,将next节点的prev属性指向x节点的prev属性,并将x的next属性清空
  4. 将x的item属性清空,以便垃圾收集器回收x对象

 

过程如图所示


image.png

ArrayList和LinkedList比较

ArrayList详解可以看我的另一篇文章:Java集合:ArrayList详解


  1. ArrayList底层基于动态数组实现,LinkedList底层基于链表实现
  2. 对于随机访问(get/set方法),ArrayList通过index直接定位到数组对应位置的节点,而LinkedList需要从头结点或尾节点开始遍历,直到寻找到目标节点,因此在效率上ArrayList优于LinkedList
  3. 对于插入和删除(add/remove方法),ArrayList需要移动目标节点后面的节点(使用System.arraycopy方法移动节点),而LinkedList只需修改目标节点前后节点的next或prev属性即可,因此在效率上LinkedList优于ArrayList。


参考


LinkedList源码(JDK 1.8)

—————END—————

 

相关文章
|
3天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
13 3
|
1天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
13 6
|
1天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
11 4
|
1天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
8 3
|
3天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
11 5
|
1天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
6 2
|
1天前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
10 2
|
3天前
|
Java
Java Set 是一个不包含重复元素的集合接口,确保每个元素在集合中都是唯一的
【10月更文挑战第14天】Java Set 是一个不包含重复元素的集合接口,确保每个元素在集合中都是唯一的。本文介绍了 Set 的独特特性和两个常用实现类:基于哈希表的 HashSet 和基于红黑树的 TreeSet。通过示例代码展示了它们如何高效地处理唯一性约束的数据。
13 3
|
3天前
|
存储 算法 Java
Java的Set集合以其严格的“不重复性”著称,使开发者既好奇又困惑
Java的Set集合以其严格的“不重复性”著称,使开发者既好奇又困惑。本文将探讨Set为何如此“挑剔”。Set接口不包含重复元素,适用于需要唯一性约束的场景。其内部通过哈希表或红黑树等数据结构和哈希算法、equals()方法来确保元素的唯一性。示例代码展示了Set如何自动过滤重复元素,体现了其高效性和便利性。
10 2
|
3天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。
【10月更文挑战第14天】从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。本文深入探讨Set的核心理念,并通过示例代码展示了HashSet和TreeSet的特点和应用场景。
8 2