单链表 双向链表 双向队列 (LinkedList 源代码解读)重点是LinkedList 源代码解读

单向链表与双向链表之间进行比较

在这里插入图片描述

单链表:在链表中的每个结点中,只是维护一个其后面的结点的地址,可以满足一定的增加结点以及删除结点的需求;

双向链表,在每个结点中,维护的是当前结点的上一个结点的地址以及当前结点的下一个结点的地址;

单链表的局限性:
有一个需求:查找当前的结点的上一个结点是什么,此时单链表是非常难以实现的,但是此时使用双向链表是十分容易实现的;

双向链表的缺点:
由于每个结点都多维护了一个指向上一个结点的指针,所以对于系统资源的消耗耗时存在的;

Java Deque 接口 双向队列

在常规队列中,元素是从后面添加的,而从前面删除的;
但是,在双向队列中,可以从前或者后插入和删除元素;

在这里插入图片描述

实现Deque的类

为了使用Deque接口的功能,我们需要使用实现接口的类:
在这里插入图片描述

由于LinkedList 一边实现了 List 接口,另一边实现了 Deque 接口,所以同时拥有了 List 的功能以及 Deque 的功能;

也就拥有了基本的集合的增删改查功能以及 Deque 的头部尾部同时可以增删改查的功能;

ArrayDeque

LinkedList (双向链表)

个人理解:List 集合提供了基础的增删改查方法,Deque 提供了双向链表的方法,两个接口的集成就形成了 LinkedList 实现类;
在这里插入图片描述

LinkedList 作为 List 集合的实现的 增

public boolean add(E e)


    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    // 将元素添加到集合的尾部
    // 这个方法是和 addLast() 方法的结果是一样的;
    // 参数 e 添加到集合中的元素
    // 返回是否添加成功
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * Links e as last element.
     */
    // 作为最后一个元素进行添加
    void linkLast(E e) {
    	// 保存最后一个元素,因为新的元素添加进来之后,最后一个元素的指针需要指向新的结点 
        final Node<E> l = last;
        // 创建新的结点,上一个结点指向以前旧的结点 l ,将元素放置进去,,后面没有元素了,所以指向的下一个元素为 null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
	    // 当 l == null 的时候,发明这个 LinkedList 是一个空的双向链表,所以链表的头部指向指新加进来的这个结点;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
小结

使用默认的添加方法,使用的是添加到双向链表的尾部

public void add(int index, E element)

完成执行位置的元素的添加,按照 add(int index, E element) 的每一句代码,搞清楚中间的调用的每一个方法,这个过程就非常简单

    public void add(int index, E element) {
        checkPositionIndex(index);
			 // 添加元素的索引是 size 的时候,放置到双向链表的尾部即可
			 // size = length + 1;
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
return index >= 0 && index <= size;
当关于双向链表的索引是小于 0 并且超过了 size 的时候,会报错

为什么添加元素到 size 的位置是正确的?
因为 size 的位置就是双向链表中的最后一个结点的下一个结点,刚好可以放置到最后一个元素的下一个,也是满足需要的;
node(index) 返回指定位置的结点

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    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;
        }
    }
void linkBefore(E e, Node succ) 前面找到的执行位置的结点之前插入新的结点,完成了指定位置插入新元素;

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
			 // 创建出来的新节点,这个新结点的前一个元素指向 succ 的 pred ;这个新节点的下一个元素指向 succ 占用了 succ 的 index 的位置,完成了按照 index 进行结点的插入操作
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 进行新结点的链接
        succ.prev = newNode;
        // pred == null 说明这是插入到了双向链表的第一个元素
        if (pred == null)
            first = newNode;
        else// pred 非空,新加点插入到了双向链表中间的某个位置了
        		 // 进行下一个方向的链接
            pred.next = newNode;
        size++;
        modCount++;
    }
小结

找到没有在指定位置的元素之前的 index 位置的结点,使得 原来 index 的结点的前一个结点链接新的结点,新的结点链接到原来 index 的结点的下一个结点;

在新加点加入的时候,一共有死四条链接需要链接起来,在创建新节点的时候,链接好了两条,所以在上面小结中的第一句话中又链接好了两条,一共是四条链接都链接好了;

上面的元素添加的过程如下面图示:(下面的过程执行结束之后看起来就像是 index 后面的元素整体向前面移动了一样,只是看起来,因为内存中使用的是指针连起来的,不是连续空间联系起来的)
在这里插入图片描述

LinkedList 作为 List 集合的实现的 删

public E remove() 删除双向链表的头结点

检索并删除此列表的头部(第一个元素)。
回报:
此列表的头部
抛出:
NoSuchElementException – 如果此列表为空
自从:
1.5

   public E remove() {
       return removeFirst();
   }

调用 removeFirst() 方法


    /**
     * Removes and returns the first element from this list.
     *
     * @return the first element from this list
     * @throws NoSuchElementException if this list is empty
     */
    public E removeFirst() {
    	 // 将 first 的结点添加一份引用
        final Node<E> f = first;
        // 当删除的头结点是 null 的时候,返回没有这个元素的异常
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

/**
 * Unlinks non-null first node f.
 */
// 将非空的头结点移除,也就是释放掉头结点
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    // 直接将头结点存放的数值置空
    f.item = null;
    // 直接将头结点指向下一个结点的指针置空
    f.next = null; // help GC
    // first 指针指向下一个结点,原来的头结点就被从双向链表中去除了,实现了删除的效果,在链表中删除一个元素的表现形式是引用置空,会有垃圾回收器进行垃圾回收,空指针会被回收的
    // 原来指向头结点的指针指向第二个结点,原来的第二个结点充当现在的第一个结点;
    first = next;
    // 如果 next == null 说明原来双向链表中只有一个头结点,删除了头结点双向链表中没有任何元素了;所以 last == null ,双向链表中都没有任何元素了,所以最后一个自然指向了 null 此时的 first 也是指向 null 的
    if (next == null)
        last = null;
    else // next 非空,此时的 next 是头结点了,头结点的前一个自然是 null ,老大结点前面没有任何节点了,自己就是老大
        next.prev = null;
    size--;
    modCount++;
    return element;
}
小结

使用 remove() 方法的时候,删除的是双向链表中的头部元素,和上面的直接添加元素不同,添加的时候,从双向链表的尾部添加数据,删除的元素的时候,没有指定元素,是从双向链表的头部删除元素;


public E remove(int index) 删除指定位置的元素

删除指定位置上面的元素

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
E unlink(Node x)

总体思路:
1、index 结点前面的第一个结点与后面的第一个结点建立双向的联系;

2、将 index 结点的 prev ,next 结点置空,从双向链表中脱落下来;

3、index 结点的 item 置空,等待垃圾回收

/**
 * Unlinks non-null node x.
 */
// 传递进来的参数是待删除的结点 前面使用 node() 方法找到的待删除结点
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;
	 // index 前面的结点与 index 后面的结点建立联系,孤立 index 结点
	 // prev == null 说明删除的是头结点
    if (prev == null) {
        // 直接移动 first 指针到达头结点后面的元素
        first = next;
    } else {// 删除的不是头结点 index 前面的结点与 index 后面的结点进行链接,将 index 的结点孤立,index 结点等待垃圾回收
        prev.next = next;
        // 待删除的 index 结点断开与前面一个结点的联系
        x.prev = null;
    }

	  // index 后面的结点与 index 前面的结点建立联系,孤立 index 结点
    if (next == null) {
    		 // last 指针指向 prev 原来的 prev 现在成为了 最后一个结点
        last = prev;
    } else {
    	 // index 后面的结点与 index 前面的结点进行链接,将 index 的结点孤立,index 结点等待垃圾回收
        next.prev = prev;
        // 待删除的 index 结点断开与后面一个结点的联系
        x.next = null;
    }

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

LinkedList 作为 List 集合的实现的 改

public E set(int index, E element) 修改执行结点的数据

public E set(int index, E element) {
	  // 检查传递进来的索引
    checkElementIndex(index);
    // 找到 index 结点
    Node<E> x = node(index);
    E oldVal = x.item;
		// 新元素的赋值
    x.item = element;
    // 返回旧的数据
    return oldVal;
}

node(int index) 返回执行索引的结点

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;
    }
}

LinkedList 作为 List 集合的实现的 查

public E get(int index) 按照索引查询结点的数据

// 输出参数:结点的索引
// 返回:索引位置的结点的数据
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

node(index) 返回 index 位置的结点

这个方法在前面介绍过具体的可以在前面查看

public E getFirst() 得到双向队列中的头结点的数据

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

public E getLast() 得到双向链表中尾结点的数据

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

LinkedList 作为 Deque 双向队列的实现的 增删改查

在这里插入图片描述

Deque 继承了 Queue ,所以 Deque 拥有 Queue 的方法,Queue 的常用方法有:
在这里插入图片描述Deque 与 Queue相比 的常用方法有:
在这里插入图片描述因为 LinkedList 实现了 Deque 所以上面的 Queue 以及 Deque 的所有方法 LinkedList 都是拥有的;使用上面的方法,可以实现双向队列的增删改查,由于源代码和上面的源代码解读过程比较相似,并且原代码比较简单,不在赘述;可以参考下面的博客进行阅读

小结

方法名称 List方法的使用效果 List
public boolean add(E e)添加结点到双向链表的尾部
public void add(int index, E element)添加结点到达指定位置
public E remove()删除双向链表的头结点
public E remove(int index)删除指定位置的结点

参考博客

搞懂 Java LinkedList 源码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值