【问题标题】:Decoding LinkedList implementation解码 LinkedList 实现
【发布时间】:2015-11-24 09:40:50
【问题描述】:

为了了解实现、操作等,我从 JDK 中复制了 LinkedList 类,并以非泛型(仅限字符串)方式进行了修改。您可以找到实际代码here。与问题相关,这里是
addLast()

public void addLast(String e){
    System.out.println("Invoked:addLast()");
    linkLast(e);
}

节点内部类

private static class Node {
     String item;
     Node next;
     Node prev;
     Node(Node prev, String element, Node next) {
         this.item = element;
         this.next = next;
         this.prev = prev;
    }
    @Override   public String toString() {
        return "Node [item=" + item + ", next=" + next + ", prev=" + prev+ "]";
    }
}

linkLast()

void linkLast(String e) {
    System.out.println("Invoked:linkLast(" + e + ")");
    final Node l = last;
    final Node newNode = new Node(l, e, null);
    System.out.println("\tCreatd new node as:" + newNode);
    last = newNode;
    System.out.println("\tSetting this node as last");
    if (l == null){
        // Applicable for 1st node. Last & First point to newNode
        first = newNode;
        System.out.println("\tLast was null, setting this node as first.");
    }          
    else{
        // Set newNode as next of previous Last.
        System.out.println("\tLast was not null, seting this node as next node of previous Last.");
        l.next = newNode;
    }
    size++;
    //System.out.println("Size of list:" + size);
    modCount++;
    System.out.println("\tMod Count:" + modCount + "\n");
}

当我执行这个类时,我得到一个 stackoverflow 错误,

public static void main(String args[]){
    LinkedListDemo obj = new LinkedListDemo();
    obj.add("B");
    obj.addFirst("A");
    obj.addLast("C");
}

输出

Invoked:add(B)
Invoked:linkLast(B)
    Creatd new node as:Node [item=B, next=null, prev=null]
    Setting this node as last   Last was null, setting this node as first.
    Mod Count:1
Invoked:addFirst()
Invoked:linkFirst(A)
    Creatd new node asNode [item=A, next=Node [item=B, next=null, prev=null], prev=null]
    Seting this node as first
    First was not null, seting this node as next node of previous First.
    Mod Count:2
Invoked:addLast()
Invoked:linkLast(C)
Exception in thread "main" java.lang.StackOverflowError
    at java.lang.String.getChars(String.java:826)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:416)at java.lang.StringBuilder.append(StringBuilder.java:132)
    at java.lang.StringBuilder.<init>(StringBuilder.java:110)
    at collections.LinkedListDemo$Node.toString(LinkedListDemo.java:570)
    at java.lang.String.valueOf(String.java:2847)
    at java.lang.StringBuilder.append(StringBuilder.java:128)
    at collections.LinkedListDemo$Node.toString(LinkedListDemo.java:570)

然后最后 3 行不断重复。 我无法弄清楚导致这种情况的原因。

更新 - 基于收到的回复
所以,如果理解正确,

Invoked:add(A)
Invoked:linkLast(A)
    Creatd new node as:Node [item=A, next=null, prev=null]
    Setting this node as last
    Last was null, setting this node as first.
    Mod Count:1
Invoked:add(B)
Invoked:linkLast(B)
    Creatd new node as:Node [item=B, next=null, prev=Node [item=A, next=null, prev=null]]
    Setting this node as last
    Last was not null, seting this node as next node of previous Last.
    Mod Count:2

在此阶段,“节点 B”仅使用一侧链接(prev)创建,其中调用了 toString()。只有在链接另一侧链接(下一个)之后。而这个双重链接是无限递归的原因,在下一次插入时。

也许这就是为什么 toString() 方法没有在它的两个超类 AbstractSequentialList、AbstractList 中实现但在 AbstractCollection 中的原因。该实现提取了一个迭代器,它迭代以打印集合。

那么,为什么它只发生在第 3 次插入时。两次插入不会导致此问题。

【问题讨论】:

    标签: java linked-list stack-overflow


    【解决方案1】:

    问题在于您的 toString 方法,它在尝试打印 prevnext 节点时递归调用自身。

    如果当前打印的Nodenext不为空,则调用next.toString(),当打印该Nodeprev时,调用原始NodetoString()再次,所以递归没有尽头。

    可能的解决方案:

    @Override
    public String toString() 
    {
        return "Node [item=" + item + ", next=" + (next==null?"null":next.item) + ", prev=" + (prev==null?"null":prev.item) + "]";
    }
    

    关于cmets:

    添加第一个节点后,列表如下所示:

    prev             next
    null <- Node1 -> null
    

    firstlast 都指向 Node1

    在 toString 中没有无限递归的风险。

    添加第二个节点后,列表如下所示:

    null <- Node1 -> <- Node2 -> null
    

    first 指 Node1,last 指 Node2

    如您所见,Node1 和 Node2 相互引用(Node1 是 Node2 的 prev,Node2 是 Node1 的 next)。因此,在第二次插入后尝试使用原始toString() 方法打印其中一个节点将导致无限递归。

    但是,由于您在执行 l.next = newNode; 行之前打印了新节点,因此在第二次插入时不会得到无限递归,只有在第三次插入时。如果将System.out.println("\tCreatd new node as:" + newNode);移动到linkLast的末尾,第二次插入会导致无限递归。

    【讨论】:

    • nextprev 永远不会为空。
    • @cadrian 我没有足够严格地遵循代码来确定这一点,所以我添加了空检查以确保安全。也就是说,根据 OP 在收到 StackOverflowError 之前获得的输出,它们可以为 null。
    • 确实你是对的。我读得不够仔细。节点在列表的每一端都是空的。
    • 不应该这样吗?如果是这样,四肢的下一个/上一个将指向哪里?
    • 那么,为什么问题只出现在第二次插入之后?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-22
    • 2011-02-27
    相关资源
    最近更新 更多