解决一次i++引发的bug

互联网 20-10-19

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

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

起因

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

分析

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

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

于是就一个个来分析:

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

代码

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

import com.google.common.collect.Lists;import java.util.List;public class Test {    public static void main(String[] args) {        // 获取Customer数据,这里就简单模拟         List<Customer> 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}复制代码

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

也就是说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}复制代码
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++,就会产生这条指令

最后

感谢各位能够看到这里,以上就是我处理这个bug的全部过程。虽然这只是一个小bug,但是这一个小小的bug还是值得学习和思考的。今后还会继续分享我所发现的bug以及知识点,如果我的文章对你有所帮助,还希望各位大佬点个关注\color{red}{点个关注}点个赞\color{red}{点个赞},再次感谢大家的支持!

相关免费学习推荐:java基础教程

以上就是解决一次i++引发的bug的详细内容,更多内容请关注技术你好其它相关文章!

来源链接:
免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
标签: i++
上一篇:php获取远程图片并下载保存到本地的方法分析 下一篇:服务器workerman怎么配置

相关资讯