LinkedList源码解析

LinkedList源码解析

简介

优缺点:

​ 优点:对于频繁的插入、删除操作,使用它比使用ArrayList效率高。

​ 缺点:进行查询操作时效率低。

​ LinkedList进行查询操作时使用折半查找。

底层原理:

​ LinkedList底层使用双向链表存储数据。

LinkedList的继承与实现(该图由Idea自动生成):

在这里插入图片描述
LinkedList 继承于AbstractSequentialList,这个抽象类实现了get()、set()、add() 和 remove()这些函数。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

源码解析

使用无参构造函数创建一个LinkedList,使用Ctrl+鼠标左键进入查看源码,可以看出LinkedList的无参构造函数没有写任何的逻辑。

所以linkedList在创建对象时没有对属性进行初始化。

LinkedList的属性

// linkedList内存放元素的大小
transient int size = 0;

// 指向第一个节点的指针,头节点
transient Node<E> first;

// 指向最后一个节点的指针,尾节点
transient Node<E> last;

Node类

Node类是LinkedList内的私有的静态内部类,可以在LinkedList内直接调用,但不能在外部调用。

链表由一个或多个节点组成,Node是链表内的一个节点

private static class Node<E> {
    
    // item为Node内存放元素的属性
    E item;
    // next指向下一个节点
    Node<E> next;
    // prev指向上一个节点
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

Node节点图示
在这里插入图片描述
链表图示
在这里插入图片描述

创建LinkedList对象

1、使用无参构造函数

public LinkedList() {
}

可以看出,LinkedList()内没有任何逻辑,所以只有在添加第一个数据时,链表才会创建,linkedList内的属性size,prev,next此时皆为空。

2、使用有参构造函数

此时参数为Collection类,Collection类为LinkedList的父辈。

LinkedList(Collection<? extends E> c)函数

public LinkedList(Collection<? extends E> c) {
    // this()方法调用无参构造函数创建linkedList对象
    this();
    // 向其中添加全部数据
    addAll(c);
}

addAll(Collection<? extends E> c)函数

public boolean addAll(Collection<? extends E> c) {
    // size为LinkedList大小,此时为0
    return addAll(size, c);
}

addAll(int index, Collection<? extends E> c)函数

// index此时为从第index处节点开始添加数据
public boolean addAll(int index, Collection<? extends E> c) {
    // 范围检查,判断是否越界,否则抛出异常
    checkPositionIndex(index);

    // Collection对象转为数组
    Object[] a = c.toArray();
    // 获取数组的长度
    int numNew = a.length;
    // 如果数组长度为0,插入数据失败
    if (numNew == 0)
        return false;

    // 前置节点,后置节点
    Node<E> pred, succ;
    // index == size时表示从链表尾部添加元素,所以前置节点为当前链表的尾节点。
    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);
        // 前置节点为null,表示为头节点(first)
        if (pred == null)
            first = newNode;
        else
            // last.next = newNode
            pred.next = newNode;
        // last = newNode
        // 令链表的最后一个节点等于新添加的节点
        pred = newNode;
    }

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

    size += numNew;
    // 修改次数+1
    modCount++;
    return true;
}

向LinkedList集合中添加元素

1、add(E e)函数
public boolean add(E e) {
    linkLast(e);
    return true;
}

linkLast(E e)函数

void linkLast(E e) {
    // 指定节点l指向last节点
    final Node<E> l = last;
    // 创建一个newNode节点,它的前置节点为l,后置节点为null
    final Node<E> newNode = new Node<>(l, e, null);
    // 指定last节点指向newNode节点
    last = newNode;
    // 此时头节点为空,所以第一个添加的节点为头节点
    if (l == null)
        first = newNode;
    else
        // 头节点不为空,则令最后一个节点指向这个新节点。
        l.next = newNode;
    // linkLast的长度+1
    size++;
    // 修改次数+1
    modCount++;
}

注意:向linkedList中添加、插入、删除数据时,主要是令前置节点与后置节点建立联系,后置节点与前置节点建立联系,二者并不冲突。

2、add(int index, E element)函数

功能:向集合中指定位置插入元素

public void add(int index, E element) {
    // 检查index范围是否越界
    checkPositionIndex(index);

    // 如果集合长度等于插入位置,则直接在链表尾部插入
    if (index == size)
        linkLast(element);
    // 否则,查询位置在index处的节点,将新节点插入node(index)节点之前
    else
        linkBefore(element, node(index));
}

linkBefore(E e, Node succ)函数

void linkBefore(E e, Node<E> succ) {
    // 当前节点的前置节点
    final Node<E> pred = succ.prev;
    // 就是使newNode插入pred节点与succ节点之间,并与pred与succ之间建立联系
    final Node<E> newNode = new Node<>(pred, e, succ);
	// 使succ节点与newNode节点建立联系,newNode为succ的前置节点
    succ.prev = newNode;
    // 如果前置节点为空,则新插入的节点成为头节点
    if (pred == null)
        first = newNode;
    else
        // pred与newNode建立联系
        pred.next = newNode;
    size++;
    modCount++;
}

从LinkedList中删除数据

remove(int index)函数

public E remove(int index) {
    // 检查index是否越界
    checkElementIndex(index);
    // 使用node(index)获取节点,然后使用下面的方法删除节点
    return unlink(node(index));
}

unlink(Node x)函数

E unlink(Node<E> x) {
    // 获取要移除的节点的元素
    final E element = x.item;
    // 获取后置节点
    final Node<E> next = x.next;
    // 获取前置节点
    final Node<E> prev = x.prev;

    // 如果前置节点为空,则令头节点指向后置节点next
    if (prev == null) {
        first = next;
    } else {
        // 令前置节点的下一个指向后置节点,去除与节点x的联系
        prev.next = next;
        // 使用垃圾回收机制
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        后置节点的上一个节点变为前置节点,去除与节点x的联系
        next.prev = prev;
        // 删除节点的上一个节点变为空 告诉给gc(垃圾回收机制)实现回收
        x.next = null;
    }

    // 删除节点的item变为null,告诉给gc实现回收
    x.item = null;
    // 集合的长度减一
    size--;
    // 修改次数+1
    modCount++;
    return element;
}

使用index获取元素

get(int index)函数

public E get(int index) {
    // 检查范围是否越界
    checkElementIndex(index);
    // 调用node函数返回节点的item
    return node(index).item;
}

node(int index)

Node<E> node(int index) {
    // 使用二分查找获取节点,只进行一次二分查找
    // size >> 1 等同于 size / 2;
    if (index < (size >> 1)) {
        // 获取头节点
        Node<E> x = first;
        // for循环遍历链表从0到index。
        // 从头开始正序遍历知道到达index
        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;
    }
}

LinkedList的遍历

1、使用for循环遍历
2、使用增强for循环遍历
3、使用迭代器遍历

LinkedList可以作为队列、双端队列、栈使用

:先进后出。

队列:队列(queue)是一种是相对于栈的一种数据结构,它是先进先出(First In First Out)。它只可以在尾部添加元素。

双端队列(Deque:double ended queue):是一种相对于队列的一种数据结构。它可以在尾部和头部插入、移除和获取。

​ 相对于普通队列,双端队列的入队和出队操作在两端都可进行。

栈(stack)、队列(queue)、双端队列(deque)

*栈和队列只是双端队列的特殊情况,它们的方法都可以使用双端队列的方法替代,使用不同的名称和方法,概念上更为清晰。看看Deque接口里面的方法*
在这里插入图片描述
该图转载自博客:LinkedList实现栈、队列或者双端队列分析

总结

LinkedList本质上与ArrayList的实现方式差不多,但ArrayList底层是操作数组,而LinkedList的底层是操作双向链表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值