深入理解Java LinkedList:原理解析与自定义实现

Java的LinkedList是一个重要的集合类,实现了双向链表的数据结构。相比于ArrayList,LinkedList在某些场景下具有更高的性能优势。本文将深入解析Java LinkedList的原理,并通过自定义实现来帮助你理解其底层机制。

什么是LinkedList?

LinkedList是Java集合框架中的一个类,它实现了List和Deque接口。与ArrayList不同,LinkedList基于双向链表的数据结构,适用于频繁插入和删除操作的场景。

LinkedList的底层实现原理

双向链表

LinkedList使用双向链表来存储元素。每个节点包含一个元素和指向前后两个节点的引用。

节点的插入和删除

在双向链表中,插入和删除操作的时间复杂度为O(1),因为只需要调整相关节点的引用,而不需要像ArrayList那样移动大量元素。

随机访问

由于需要沿着链表遍历节点,LinkedList的随机访问时间复杂度为O(n),不如ArrayList的O(1)高效。

自定义LinkedList的实现

以下是一个自定义LinkedList的示例代码,帮助你理解其底层原理。

自定义LinkedList类

java 复制代码
public class CustomLinkedList<E> {
    private Node<E> head;
    private Node<E> tail;
    private int size;

    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 void add(E e) {
        Node<E> newNode = new Node<>(tail, e, null);
        if (tail == null) {
            head = newNode;
        } else {
            tail.next = newNode;
        }
        tail = newNode;
        size++;
    }

    // 获取指定位置的元素
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    // 获取节点
    Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = head;
            for (int i = 0; i < index; i++) {
                x = x.next;
            }
            return x;
        } else {
            Node<E> x = tail;
            for (int i = size - 1; i > index; i--) {
                x = x.prev;
            }
            return x;
        }
    }

    // 删除指定位置的元素
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    // 删除节点
    private E unlink(Node<E> x) {
        E element = x.item;
        Node<E> next = x.next;
        Node<E> prev = x.prev;

        if (prev == null) {
            head = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            tail = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        return element;
    }

    // 检查索引
    private void checkElementIndex(int index) {
        if (!isElementIndex(index)) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    // 获取大小
    public int size() {
        return size;
    }
}

测试自定义LinkedList

java 复制代码
public class CustomLinkedListTest {
    public static void main(String[] args) {
        CustomLinkedList<String> list = new CustomLinkedList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        System.out.println("Element at index 1: " + list.get(1));
        System.out.println("Size of list: " + list.size());

        list.remove(1);
        System.out.println("Element at index 1 after removal: " + list.get(1));
        System.out.println("Size of list after removal: " + list.size());
    }
}

详细讲解自定义LinkedList的各个部分

构造函数

构造函数初始化了头节点和尾节点,并设置初始大小为0。

添加元素

添加元素时,会创建一个新节点并将其添加到链表末尾。

java 复制代码
public void add(E e) {
    Node<E> newNode = new Node<>(tail, e, null);
    if (tail == null) {
        head = newNode;
    } else {
        tail.next = newNode;
    }
    tail = newNode;
    size++;
}
添加元素图解

假设链表初始为空,依次添加元素 "Apple"、"Banana" 和 "Orange"。

  1. 添加 "Apple" 时:

    null <- [Apple] -> null

  2. 添加 "Banana" 时:

    null <- [Apple] <-> [Banana] -> null

  3. 添加 "Orange" 时:

    null <- [Apple] <-> [Banana] <-> [Orange] -> null

获取元素

通过索引获取元素,首先检查索引是否合法,然后遍历链表找到对应的节点。

java 复制代码
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

获取节点

根据索引值从头或尾开始遍历链表,提高效率。

java 复制代码
Node<E> node(int index) {
    if (index < (size >> 1)) {
        Node<E> x = head;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    } else {
        Node<E> x = tail;
        for (int i = size - 1; i > index; i--) {
            x = x.prev;
        }
        return x;
    }
}

删除元素

删除元素时,首先找到对应的节点,然后调整前后节点的引用,并将节点置为null以便垃圾回收。

java 复制代码
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
删除元素图解

假设链表当前状态如下,删除元素 "Banana"(即索引1的元素):

复制代码
null <- [Apple] <-> [Banana] <-> [Orange] -> null
  1. 找到 "Banana" 的节点。

  2. 调整 "Apple" 和 "Orange" 节点的引用,使它们直接相连:

    null <- [Apple] <-> [Orange] -> null

  3. 将 "Banana" 节点置为null,以便垃圾回收。

删除节点

具体的节点删除操作。

java 复制代码
private E unlink(Node<E> x) {
    E element = x.item;
    Node<E> next = x.next;
    Node<E> prev = x.prev;

    if (prev == null) {
        head = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        tail = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    return element;
}

检查索引

确保索引在合法范围内。

java 复制代码
private void checkElementIndex(int index) {
    if (!isElementIndex(index)) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

获取大小

获取链表的大小。

java 复制代码
public int size() {
    return size;
}

总结

通过自定义LinkedList,我们深入理解了LinkedList的底层实现原理,包括其双向链表结构、元素插入与删除操作以及访问元素的方式。理解这些底层原理,有助于我们在使用LinkedList时更加高效和合理。

希望本文对你理解Java LinkedList的底层原理有所帮助。如果你喜欢这篇文章,请点赞并分享,关注我们的博客以获取更多关于Java编程和软件开发的精彩内容!

相关推荐
浮游本尊11 小时前
Java学习第22天 - 云原生与容器化
java
渣哥13 小时前
原来 Java 里线程安全集合有这么多种
java
间彧13 小时前
Spring Boot集成Spring Security完整指南
java
间彧13 小时前
Spring Secutiy基本原理及工作流程
java
Java水解14 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆16 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学17 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole17 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊17 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端