Jadual Kandungan
起因
分析
代码
建模
解释
延伸一下
最后
Rumah Java JavaAsas 解决一次i++引发的bug

解决一次i++引发的bug

Oct 19, 2020 pm 05:40 PM
java

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

解决一次i++引发的bug

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

解决一次i++引发的bug

起因

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

分析

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

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

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

解决一次i++引发的bug

于是就一个个来分析:

  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数据,这里就简单模拟
        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;
    }
}复制代码
Salin selepas log masuk

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

建模

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

public class Test2 {    public static void main(String[] args) {        int index = 3;
        index = index++;
        System.out.println(index);
    }
    
}复制代码
Salin selepas log masuk

我们将上面的业务逻辑简化成这样一个模型,那么这个结果毫无意外的是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}复制代码
Salin selepas log masuk

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

解决一次i++引发的bug

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

解决一次i++引发的bug

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

解决一次i++引发的bug

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

解决一次i++引发的bug

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

解决一次i++引发的bug

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

解决一次i++引发的bug

也就是说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}复制代码
Salin selepas log masuk

可以发现,这里就没有最后一步的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}复制代码
Salin selepas log masuk

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

最后

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

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

Atas ialah kandungan terperinci 解决一次i++引发的bug. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
1 bulan yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
1 bulan yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Akan R.E.P.O. Ada Crossplay?
1 bulan yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Nombor Sempurna di Jawa Nombor Sempurna di Jawa Aug 30, 2024 pm 04:28 PM

Panduan Nombor Sempurna di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor Perfect dalam Java?, contoh dengan pelaksanaan kod.

Weka di Jawa Weka di Jawa Aug 30, 2024 pm 04:28 PM

Panduan untuk Weka di Jawa. Di sini kita membincangkan Pengenalan, cara menggunakan weka java, jenis platform, dan kelebihan dengan contoh.

Nombor Smith di Jawa Nombor Smith di Jawa Aug 30, 2024 pm 04:28 PM

Panduan untuk Nombor Smith di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor smith di Jawa? contoh dengan pelaksanaan kod.

Soalan Temuduga Java Spring Soalan Temuduga Java Spring Aug 30, 2024 pm 04:29 PM

Dalam artikel ini, kami telah menyimpan Soalan Temuduga Spring Java yang paling banyak ditanya dengan jawapan terperinci mereka. Supaya anda boleh memecahkan temuduga.

Cuti atau kembali dari Java 8 Stream Foreach? Cuti atau kembali dari Java 8 Stream Foreach? Feb 07, 2025 pm 12:09 PM

Java 8 memperkenalkan API Stream, menyediakan cara yang kuat dan ekspresif untuk memproses koleksi data. Walau bagaimanapun, soalan biasa apabila menggunakan aliran adalah: bagaimana untuk memecahkan atau kembali dari operasi foreach? Gelung tradisional membolehkan gangguan awal atau pulangan, tetapi kaedah Foreach Stream tidak menyokong secara langsung kaedah ini. Artikel ini akan menerangkan sebab -sebab dan meneroka kaedah alternatif untuk melaksanakan penamatan pramatang dalam sistem pemprosesan aliran. Bacaan Lanjut: Penambahbaikan API Java Stream Memahami aliran aliran Kaedah Foreach adalah operasi terminal yang melakukan satu operasi pada setiap elemen dalam aliran. Niat reka bentuknya adalah

TimeStamp to Date in Java TimeStamp to Date in Java Aug 30, 2024 pm 04:28 PM

Panduan untuk TimeStamp to Date di Java. Di sini kita juga membincangkan pengenalan dan cara menukar cap waktu kepada tarikh dalam java bersama-sama dengan contoh.

Program Java untuk mencari kelantangan kapsul Program Java untuk mencari kelantangan kapsul Feb 07, 2025 am 11:37 AM

Kapsul adalah angka geometri tiga dimensi, terdiri daripada silinder dan hemisfera di kedua-dua hujungnya. Jumlah kapsul boleh dikira dengan menambahkan isipadu silinder dan jumlah hemisfera di kedua -dua hujungnya. Tutorial ini akan membincangkan cara mengira jumlah kapsul yang diberikan dalam Java menggunakan kaedah yang berbeza. Formula volum kapsul Formula untuk jumlah kapsul adalah seperti berikut: Kelantangan kapsul = isipadu isipadu silinder Dua jumlah hemisfera dalam, R: Radius hemisfera. H: Ketinggian silinder (tidak termasuk hemisfera). Contoh 1 masukkan Jejari = 5 unit Ketinggian = 10 unit Output Jilid = 1570.8 Unit padu menjelaskan Kirakan kelantangan menggunakan formula: Kelantangan = π × r2 × h (4

Bagaimana untuk menjalankan aplikasi boot musim bunga pertama anda di Spring Tool Suite? Bagaimana untuk menjalankan aplikasi boot musim bunga pertama anda di Spring Tool Suite? Feb 07, 2025 pm 12:11 PM

Spring Boot memudahkan penciptaan aplikasi Java yang mantap, berskala, dan siap pengeluaran, merevolusi pembangunan Java. Pendekatan "Konvensyen Lebih Konfigurasi", yang wujud pada ekosistem musim bunga, meminimumkan persediaan manual, Allo

See all articles