首页后端开发其他后端知识对于i++引发的bug问题怎样解决

对于i++引发的bug问题怎样解决

时间2024-03-27 17:04:03发布访客分类其他后端知识浏览613
导读:相信很多人对“对于i++引发的bug问题怎样解决”都不太了解,下面小编为你详细解释一下这个问题,希望对你有一定的帮助 java基础教程栏目为大家介绍i++引发的bug。大家好,作为日常写bug修bug的我,...
相信很多人对“对于i++引发的bug问题怎样解决”都不太了解,下面小编为你详细解释一下这个问题,希望对你有一定的帮助


java基础教程栏目为大家介绍i++引发的bug。

大家好,作为日常写bug修bug的我,今天给大家带来前几天刚刚修复的一个事故。不得不承认,有我的地方总是会有这么多bug。

起因

故事的开始发生在前几天,有一个不是很常用的导出功能,被用户反馈出,不管条件是怎么样,导出的数据只有一条,但是实际上根据条件查询是有很多数据,而且页面中也查询出很多数据。(这个问题已经被修复了,所以当时的Kibana日志也找不到了)于是放下手上的工作,投入其中来看这个问题。

分析

从问题的描述中来分析,那么只可能出现在以下情况:

  1. 根据搜索条件查询出来的记录只有一条。
  2. 对查询出来的数据进行相关业务处理,导致最后的结果只有一条。
  3. 文件导出组件的逻辑处理之后,导致结果只有一条。

题外话
写到了这里,突然想到了一个经典面试题,MQ消息丢失的场景原因分析。哈哈哈,其实大致上也是这么几个角度分析。(有机会来写MQ的文章)
题外话

于是就一个个来分析:

  1. 找到相关业务的SQL以及对应的参数,查询可得,数据不止1条,所以第一种情况可以排除。
  2. 中间业务当中有涉及到相关权限、数据敏感等,将这些都放开之后,还是只有1条数据。
  3. 文件导出组件在接收到数据的时候,打印出的日志也显示只有一条,那么可以说明肯定中间相关业务的逻辑发生了问题。

由于这段代码都是写在一整个方法里面,导致Arthas排查起来就比较困难,只好一步步设置日志进行排查。(所以,如果是一大段逻辑,建议是拆分成duoge 子方法,一来在写的时候思路明确,有一种模块化的概念,至于方法复用什么的我就不多提了,基本操作;二是一但发生问题,排查起来也会方便点,经验之谈)。
最终定位到一个for循环里面。

代码

话不多说,我们直接来看代码。众所周知,我向来是一个很保护公司代码的人,所以,我在这里又不得不给大家模拟一下了。从问题的情况来看,是导出的对象记录是空

import com.google.common.collect.Lists;
    import java.util.List;
public class Test {
    public static void main(String[] args) {
            // 获取Customer数据,这里就简单模拟
        ListCustomer>
     customerList = Lists.newArrayList(new Customer("Java"), new Customer("Showyool"), new Customer("Soga"));
            int index = 0;
    
        String[][] exportData = new String[customerList.size()][2];
        for (Customer customer : customerList) {
    
            exportData[index][0] = String.valueOf(index);
    
            exportData[index][1] = customer.getName();
    
            index = index++;

        }
    
        System.out.println(JSON.toJSONString(exportData));

    }

}
class Customer {
    public Customer(String name) {
            this.name = name;

    }
        private String name;
    public String getName() {
            return name;

    }
    public void setName(String name) {
            this.name = name;

    }

}
复制代码

这段代码看起来好像也没什么的,就是将Customer集合转换成一个字符串二维数组。但是输出结果显示:这就符合我们说的,查询出来有多条,但是输出只有1条。
仔细观察一下,我们可以发现,输出的数据显示都是最后一条,也就是说,Customer这个集合每次遍历的时候,都是后者将前者进行覆盖,也就是说,这个index的下标一直没有变化过,一直是0。

建模

这样看来,我们的这个自增确实有点问题,那么我们再简单来写一个模型

public class Test2 {
    public static void main(String[] args) {
            int index = 3;
    
        index = index++;
    
        System.out.println(index);

    }

    
}
复制代码

我们将上面的业务逻辑简化成这样一个模型,那么这个结果毫无意外的是3。

解释

那么我们执行一下javap,看看JVM字节码是如何解释:

javap -c Test2

Compiled from "Test2.java"public class com.showyool.blog_4.Test2 {
      public com.showyool.blog_4.Test2();
    
    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."init>
    ":()V
       4: return

  public static void main(java.lang.String[]);
    
    Code:       0: iconst_3       1: istore_1       2: iload_1       3: iinc          1, 1
       6: istore_1       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;

      10: iload_1      11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      14: return}
    复制代码

这里我简单讲一下这里的JVM字节码指令(后面有机会再详细写写文章)
首先我们需要先知道这里存在两个概念,操作数栈和局部变量表,这两者是存在虚拟机栈当中栈帧(stack frame)当中的一些数据结构,如图:

我们可以简单的理解为,操作数栈的作用是存放数据并且在栈中进行计算数据,而局部变量表则是存放变量的一些信息。
然后我们来看看上面的指令:
0: iconst_3 (先将常量3压入栈)

1: istore_1 (出栈操作,将值赋给第一个参数,也就是将3赋值给index)

2: iload_1 (将第一个参数的值压入栈,也就是将3入栈,此时栈顶的值为3)

3: iinc 1, 1 (将第一个参数的值进行自增操作,那么此时index的值是4)

6: istore_1 (出栈操作,将值赋给第一个参数,也就是将3赋值给index)

也就是说index这个参数的值是经历了index-> 3-> 4-> 3,所以这样一轮操作之后,index又回到了一开始赋值的值。

延伸一下

这样一来,我们发现,问题其实出在最后一步,在进行运算之后,又将原先栈中记录的值重新赋给变量,覆盖掉了 如果我们这样写:

public class Test2 {
    public static void main(String[] args) {
            int index = 3;
    
        index++;
    
        System.out.println(index);

    }


}


Compiled from "Test2.java"public class com.showyool.blog_4.Test2 {
      public com.showyool.blog_4.Test2();
    
    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."init>
    ":()V
       4: return

  public static void main(java.lang.String[]);
    
    Code:       0: iconst_3       1: istore_1       2: iinc          1, 1
       5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;

       8: iload_1       9: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      12: return}
复制代码

可以发现,这里就没有最后一步的istore_1,那么在iinc之后,index的值就变成我们预想的4。
还有一种情况,我们来看看:

public class Test2 {
    public static void main(String[] args) {
            int index = 3;
    
        index = index + 2;
    
        System.out.println(index);

    }


}


Compiled from "Test2.java"public class com.showyool.blog_4.Test2 {
      public com.showyool.blog_4.Test2();
    
    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."init>
    ":()V
       4: return

  public static void main(java.lang.String[]);
    
    Code:       0: iconst_3       1: istore_1       2: iload_1       3: iconst_2       4: iadd       5: istore_1       6: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;

       9: iload_1      10: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      13: return}
    复制代码

0: iconst_3 (先将常量3压入栈)
1: istore_1 (出栈操作,将值赋给第一个参数,也就是将3赋值给index)
2: iload_1 (将第一个参数的值压入栈,也就是将3入栈,此时栈顶的值为3)
3: iconst_2 (将常量2压入栈, 此时栈顶的值为2,2在3之上)
4: iadd (将栈顶的两个数进行相加,并将结果压入栈。2+3=5,此时栈顶的值为5)
5: istore_1 (出栈操作,将值赋给第一个参数,也就是将5赋值给index)

看到这里各位观众老爷肯定会有这么一个疑惑,为什么这里的iadd加法操作之后,会影响栈里面的数据,而先前说的iinc不是在栈里面操作?好的吧,我们可以看看JVM虚拟机规范当中,它是这么描述的:

指令iinc对给定的局部变量做自增操作,这条指令是少数几个执行过程中完全不修改操作数栈的指令。它接收两个操作数: 第1个局部变量表的位置,第2个位累加数。比如常见的i++,就会产生这条指令

看到这里,我们知道,对于一般的加法操作之后复制没啥问题,但是使用i++之后,那么此时栈顶的数还是之前的旧值,如果此刻进行赋值就会回到原来的旧值,因为它并没有修改栈里面的数据。所以先前那个bug,只需要进行自增不赋值就可以修复了。


以上就是关于“对于i++引发的bug问题怎样解决”的介绍了,感谢各位的阅读,希望文本对大家有所帮助。如果想要了解更多知识,欢迎关注网络,小编每天都会为大家更新不同的知识。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

i++java

若转载请注明出处: 对于i++引发的bug问题怎样解决
本文地址: https://pptw.com/jishu/654357.html
foreach和for在JS中foreach的用法上的区别在哪 es6中两个数组对象如何在js中匹配

游客 回复需填写必要信息