背景:
如图,有两个链表,t和s。现在,想在t链表的末尾插入s。
迭代的写法大家都不陌生:
public static void main(String[] args) {
ListNode t = new ListNode(1, new ListNode(2, null));
ListNode s = t;
ListNode e = new ListNode(3, null);
// 区别
while (t.next != null) {
t = t.next;
}
t.next = e;
System.out.println(s);
}
那么,程序运行时,jvm的堆栈是怎么变化的?
之所以有这个疑问,是因为我写的时候,偶然用了第一种方法写,发现没有奏效,就去想堆栈了
// 无法改值
public static void main(String[] args) {
ListNode t = new ListNode(1, new ListNode(2, null));
ListNode s = t;
ListNode e = new ListNode(3, null);
// 区别
while (t != null) {
t = t.next;
}
t= e;
System.out.println(s);
}
// 可以改变值
public static void main(String[] args) {
ListNode t = new ListNode(1, new ListNode(2, null));
ListNode s = t;
ListNode e = new ListNode(3, null);
// 区别
while (t.next != null) {
t = t.next;
}
t.next = e;
System.out.println(s);
}
两段代码,不同点就在于,循环体,第一代代码,是判断当前的节点是否为空,不为空就遍历到下一节点。第二段代码,判断当前的节点的下一节是否为空,不为空则遍历到下一节点。
看起来,好像差不多,但实际,如果运行,打印出来,第一段,s打印出来的是1–>2; 第二段,s打印出来的是1–>2–>3;
那么为什么会出现这样的情况呢?就是对java的变量引用和堆栈的理解不够深;
简单抽象成以下:jvm里面有堆和栈;堆里存储实体对象,栈里存放变量和值
具体可以这么理解,比如:ListNode a=new ListNode(1,null);在jvm就是这样:
具体解读:
a存在栈中,new出来的具体对象存在堆中;a实际存的是地址,地址指向这个对象。对象里的val和next同理;注意,这里,next变量是null,不占用堆和栈。
搞懂这个之后,我们再看这串代码:
// 无法改值
public static void main(String[] args) {
ListNode t = new ListNode(1, new ListNode(2, null));
ListNode s = t;
ListNode e = new ListNode(3, null);
// 区别
while (t != null) {
t = t.next;
}
t= e;
System.out.println(s);
}
// 可以改变值
public static void main(String[] args) {
ListNode t = new ListNode(1, new ListNode(2, null));
ListNode s = t;
ListNode e = new ListNode(3, null);
// 区别
while (t.next != null) {
t = t.next;
}
t.next = e;
System.out.println(s);
}
首先,将在堆里的对象,我们抽象为节点。
s的值和t的值一样,都是节点1的地址值,可以理解为都指向节点1;
t.next就是节点2的地址值;
因此t=t.next,找到了节点2的地址值,并且赋给当前变量t,以此来完成指针移动。
那么,两端代码的差异到底是什么呢?
第一段代码中,t=null的前一次迭代情况是什么呢?是遍历2节点的时候:
也就是t等于2节点的地址值,t.next=null;s保存的还是节点1的地址值;
当我们继续遍历:t=t.next ,也就是t=null当我们运行t=e,e就是节点3的位置:你会发现s的关系没有变,还是原来的,s=address_a,addreaa_a.next=address_2;
那么如果是第二段代码呢?
回到这里:遍历到2节点的时候。t.next=null为true,那么,就会跳出循环,执行t.next=e;
也就是,栈中会出现一个next变量,存的是节点3的地址值。
这里就不画图了:上图的绿色就是此时的t,每有执行t.next=e的时候,t.next=null,因此栈中暂时不会有next这个变量,因为null变量不占用堆栈。当执行t.next=e的时候,next变量才入栈,且为节点3的地址值;
由此:s存的是节点1的地址值,节点1中的next存的是节点2的地址值,节点2的next存的是节点3的地址值;
总结:
第一段代码: t=null时候,s和原来的链表是一样的。最后一个节点的next指向null,在堆栈中都不占用位置。之所以会产生让这个时候的t指向新的节点,能改变s的莫阶段,是因为,把这个时候最后一个节点的next存在了栈中,所以,你以为,让next指向新的节点,应该是能成功的。但其实,next=null的时候,next在堆栈中都不占用位置。当你的t指向null的时候,原来的链表next是不占用位置der。