01Java学习之手工实现LinkedList双向链表(存疑)

LinkedList容器

LinkedList底层用双向链表实现的存储。

特点:查询效率低,增删效率高,线程不安全。 在频繁使用增删的情况下,一把使用LinkedList。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。

  • 容器中所有的比较操作都用equals方法,一般不用==实现。
  • 上面说的指针实质上就是对象,这个对象存储的是节点的地址。

下面是LinkedList的双链表存储结构图:

在这里插入图片描述

往往链表的第一个节点是不放数据的,只存储两个指向数据的指针,且头指针必须存在。

下面根据程序分析LinkedList:

package mylinkedlist;

/**
 * 测试 LinkedList 双向链表的使用
 *
 * @author 发达的范
 * @date 2020/11/09 21:45
 */
public class TestLinkedList01 {
    
    
    private Node First;//头指针
    private Node Last;//尾指针
    private int size;

    public void add(Object object) {
    
    
        Node node = new Node(object);//创建一个新的链表节点
        if (First == null) {
    
    //如果是第一个节点
            node.Previous = null;
            node.Next = null;
            First = node;//头指针指向新添加这个节点
            Last = node;//尾指针指向新添加这个的节点
        } else {
    
    //如果不是第一个节点
            node.Previous = Last;//新添加的Previous的头指向上一个节点
            node.Next = null;
            Last.Next = node;//此处有疑问!!!
            Last = node;//尾指针指向这个新添加的节点
            First.Previous = Last;//头尾相连
            //Last.Next = First;
        }
        size++;
    }

    @Override
    public String toString() {
    
    
        StringBuilder stringBuilder = new StringBuilder("[");
        Node temp = First;
        while (temp != null) {
    
    
            stringBuilder.append(temp.element + ",");
            temp = temp.Next;
        }
        stringBuilder.setCharAt(stringBuilder.length() - 1, ']');
        return stringBuilder.toString();
    }

    public static void main(String[] args) {
    
    
        TestLinkedList01 testLinkedList01 = new TestLinkedList01();
        testLinkedList01.add("fa");
        testLinkedList01.add("da");
        testLinkedList01.add("de");
        testLinkedList01.add("fan");
        System.out.println(testLinkedList01);//此处有疑问
    }
}
package mylinkedlist;

/**
 * @author 发达的范
 * @date 2020/11/09 21:48
 * @description
 */
public class Node {
    
    
     Node Previous;
     Node Next;
     Object element;

    public Node(Node Previous, Node nodeNest, Object object) {
    
    
        this.Previous = Previous;
        this.Next = nodeNest;
        this.element = object;
    }

    public Node(Object object) {
    
    
        this.element = object;
    }
}

运行结果:在这里插入图片描述

虽然运行成功,但是我还有一些问题

  • 问题1:上面TestLinkedList01类中的第24行代码,Last.Next = node;这句的功能是把新节点和原来链表的尾部节点连接起来,但是问题就出在,当添加第一个节点的时候,执行了Last = node;语句,此时Last指向第一个节点,而并不是等于第一个节点本身,在添加第二个节点的时候,没有让第一个节点的Next指向下一个节点,却让这个指针Last的Next指向下一个节点,这样能把这个链表连接起来吗?

    问题1 我的猜想是指针Last指向的地址是第一个节点,指针Last的Next在计算机的内存中就是第一个节点的的Next指针,所以才能连接起来。

  • 问题2:输出链表全部内容时,没有调用toString()方法也能输出,这是为什么?

    我猜想的是这可能是个线程的方法,但是没有证据(手动狗头)。

带着这两个问题,我开始Debug

  1. 我在这两处设置两个断点,如下:

在这里插入图片描述

  1. 点击小虫子,得到下面的对象的值:

在这里插入图片描述

从上面的对象的值可见,再添加第一个节点时,第一个节点First指针指向的地址是Node@488,最后一个节点Last指针指向的地址也是Node@488,由此可见第一个节点的内存地址是Node@488。

  1. 继续执行:
    在这里插入图片描述

重点来了,更新尾指针Last前,此时Last指针指向的地址仍是第一个节点的地址Node@488,而Last.Next指针指向的地址是Node@497,正是第二个节点的地址,竟然连起来了,也就是说我的猜想是正确的。

  1. 继续运行:
    在这里插入图片描述

更新尾指针Last后,看见First.Previous指针指向的地址已经变成了Node@497,正是尾部节点(第二个节点)的地址,Last.Next指针指向的地址已经变成了Node@488,正是第一个节点的地址,但是现在并不能完全说明已经首尾相连了,因为此时只有两个节点,下面添加第三个节点。

  1. 继续运行: 在这里插入图片描述

    可见,更新尾指针Last前的Last.Next指针指向的地址是Node@510,说明第三个节点连接上了。

在这里插入图片描述

更新尾指针Last后,看见,此时First.Previous指针指向的地址已经变成了Node@510,正是尾部节点(第三个节点)的地址,Last.Next指针指向的地址已经变成了Node@488,正是第一个节点的地址,证明这个链表已经首尾相连,且连接正常。

这部分使用设置断点的方法来查看内存中链表是否正确创建的过程,阅读起来可能会有点繁琐,但是有助于初学者从内存中理解链表的存储过程,对双向链表有更深刻的理解。


2020/11/11 15:00更新 :

关于add()方法中添加节点时最后有一个首尾相连的操作的补充:

注:可以把头指针指向尾部节点,但是不能把尾指针指向头部节点。因为一旦形成一个环,使用toString()方法时就会报错:
在这里插入图片描述
这是堆栈溢出的错误,这正是把链表连接成了一个环导致的,因为toString()方法里面的while循环没有终止条件,所以就一直向堆栈中放置数据,最终导致堆栈溢出。
解决方法:删除Last.Next = First;即可。


2020/11/11 21:58更新 :
关于LinkedList实现的全部代码我已经push到个人github中了,您如果有需要可以自取,地址是:

https://github.com/fanfada/Testing-the-LinkedList-of-Java.git

当自己做出来的时候,回头看看,觉得链表也不是很难。下面说几个注意点:

  • 重复使用的部分封装成一个方法,减少代码的冗余度。
  • 使用泛型规范化容器的使用。
  • 删除/添加节点之后节点数量要进行相应的改变。
  • 在链表中插入节点不同于直接添加节点,可以在头部和中间进行插入,在结尾插入就显得没必要了,直接添加add()即可。
  • 善用Debug!

猜你喜欢

转载自blog.csdn.net/fada_is_in_the_way/article/details/109609357