天天看點

從位元組碼的角度徹底分析 java i++和++i(附例題)1.第一個問題2.從位元組碼的角度分析:3.關于效率4.例題分析參考網址

文章目錄

  • 1.第一個問題
  • 2.從位元組碼的角度分析:
  • 3.關于效率
  • 4.例題分析
  • 參考網址

1.第一個問題

之前隻知道,i++是先指派再自增,++i是先自增再指派,那麼如果是下面這種情況會輸出什麼呢

int i = 0;
i = i++;
System.out.println(i);
           

會輸出多少?

答案是0

2.從位元組碼的角度分析:

先說結論:不論是i++還是++i,都有個共同點是先自增。

對于:i++,先自增,再用之前的值給目标變量指派
對于:++i,先自增,再用自增之後的值給目标變量指派
           

ok,下面從位元組碼的角度講清楚為什麼是這樣:

首先分析剛才的代碼:檔案:Test.java

public class Test {
    public static void main(String[] args) {
        int i = 0;
        i = i++;
        System.out.println(i);
    }
}
           

終端

(Terminal)

進入檔案所在檔案夾,利用

javac Test.java

指令将.java編譯成位元組碼.class檔案,然後利用

javap -v Test.class

輸出帶詳細資訊的反彙編

從位元組碼的角度徹底分析 java i++和++i(附例題)1.第一個問題2.從位元組碼的角度分析:3.關于效率4.例題分析參考網址

這裡隻看code部分:

//iconst_<x>, 将常量x加載到操作數棧中
     //istore_<n>,将操作數棧中的頂部數值存儲到局部變量表中索引為n(slot_n)的位置
     //iload_<n>: 将(slot_n)上的值加載到操作數棧Code:
 Code:
         stack=2, locals=2, args_size=1  //棧深度為2,操作數個數為2(包括了args)
         0: iconst_0   // 将常量0加載到操作數棧中
         1: istore_1  //将操作數棧中的頂部數值存儲到slot_1的位置
         2: iload_1   //slot_1上的值加載到操作數棧
         3: iinc          1, 1  //iinc用于實作局部變量的自增操作。在所有位元組碼指令中,隻有該指令可直接用于操作局部變量
                                 //iinc slot_ , number ,即對指定slot_n的變量進行+=number的操作,直接對局部變量表的元素進行累加,而不是在棧中。
         6: istore_1  //将操作數棧中的頂部數值存儲到slot_1的位置
         7: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_1  //slot_1上的值加載到操作數棧
        11: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
        14: return
           

畫圖表示:

注意:

iinc

是直接對

局部變量表

的元素進行累加,而不是在棧中

重點在于2、3兩步, 先把原來的值0加載到操作數棧,然後對局部變量表中的i自增;【第3步iinc包括了三條指令】然後第6步将操作數棧中的值(原來的值)指派給局部變量表中的目标變量(i)

是以是

先自增再指派

,隻不過把沒自增之前的值先暫時儲存到了操作數棧中,然後用其指派,是以

看起來好像是先指派再自增一樣

從位元組碼的角度徹底分析 java i++和++i(附例題)1.第一個問題2.從位元組碼的角度分析:3.關于效率4.例題分析參考網址

那如果是++i呢?

public class Test {
    public static void main(String[] args) {
        int i = 0;
        i = ++;
        System.out.println(i);
    }
}
//輸出:1
           

反彙編:

Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iinc          1, 1
         5: iload_1
         6: istore_1
         7: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_1
        11: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
        14: return
           

重點還是在2、3兩步,這次是先對局部變量表中的i(0)自增,再把自增後的值(1)加載到操作數棧;然後第6步将操作數棧中的值(自增後的值)指派給局部變量表中的目标變量(i);

是以,++i和i++都是先自增再指派,指派都是将操作數棧中的值給局部變量表中的目标變量指派,不同的是2、3兩步中,一個是将自增之前的值放進操作數棧,一個是将自增之後的值放進操作數棧。

從位元組碼的角度徹底分析 java i++和++i(附例題)1.第一個問題2.從位元組碼的角度分析:3.關于效率4.例題分析參考網址

那麼我們給另外的變量指派也是這樣嗎?是的

public class Test {
    public static void main(String[] args) {
        int i = 0;
        int a = 0;
        a = i++;
        System.out.println(i);
    }
}
           

反彙編:

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_0
         3: istore_2
         4: iload_1
         5: iinc          1, 1
         8: istore_2
         9: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: iload_1
        13: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
        16: return
           

再繼續探究,i=i+1呢?

int i = 0;
 i = i + 1;
 System.out.println(i);
           

反彙編:把iinc變成了3、4兩步,在操作數棧上完成

iinc指令存在意義

Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: iconst_1
         4: iadd
         5: istore_1
         6: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: iload_1
        10: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
        13: return
           

3.關于效率

之前看過一篇文章說++i比i++效率高,具體原因是:

i++:取出i,複制i,增加i,傳回副本;
++i:取出i,增加i,傳回i;
           

i++要增加一個副本,無疑是要多耗記憶體,效率要低一點。

然後今天又看到有人說對于底層c語言來說是這樣的,還有對于c++來說也是++i比i++多了一步

class INT;   
//++i 的版本   
INT INT::operator++()   
{   
    *this = *this + 1;   
    return *this;   
}   
//i++ 的版本   
const INT INT::operator ++(int)   
{   
    INT oldvalue = *this;   
    *this = *this + 1;   
    return oldvalue; 
}   
           

但是對于java來說,從上一節的分析就能看出,他們的位元組碼是一樣的,都是 iinc,是以效率上沒有差别

4.例題分析

先聲明:一個變量也是表達式,多個表達式的加減法運算都是從左到右進行的

int a = 0;
int b = a + a * 2;
           

因為有乘法,是以

a * 2

優先組成表達式,而不是

a + a

組成表達式,也就是說總體上可以分為兩個表達式:

“a” 表達式 和 “a * 2” 表達式

,這兩個表達式相加肯定

從左到右

計算,先算完a表達式的結果,再算a * 2表達式的結果。

例1

int i = 0;
i = i++; 
System.out.println("i = " + i); 
//0
           

例2

int a = 2; 
int b = (3 * a++) + a;
System.out.println(b);
//9
           

int b = (3 * a++) + a;a++後,a = 3,并傳回自增之前的值2,是以此時表達式為:

int b = (3 * 2) + a;此時a的值已經是3了,表達式又變為:

int b = (3 * 2) + 3; 是以b = 9

例3

int a = 2; 
int b = a + (3 * a++);
System.out.println(b);
//8
           

一個變量也是表達式,多個表達式的加減法運算都是從左到右進行的,這個理論可能不能深刻體會,但是把代碼稍微改一下就能了解了,如下:

int b = (a * 1) + (3 * a++) 這個代碼和 int b = a + (3 * a++) 是一樣的,沒有差別,但是看(a *1)你就很容易的知道要先算a * 1這個表達式,表達式的結果為2。

是以,雖然 int b = a + (3 * a++) 中前面的a隻是一個變量,但他也是一個表達式,a這個表達式和(3 * a++)這個表達式進行相加,多個表達式的運算都是

從左到右

進行的,是以先算a這個表達式,a表達式計算結果為2,是以表達式變成:

int b = 2 + (3 * a++) 然後是a自增并傳回自增之前的值2,是以表達式又變為:

int b = 2 + (3 * 2);是以結果為8。此時a的值為3

從位元組碼角度看呢:

Code:
      stack=3, locals=3, args_size=1
         0: iconst_2
         1: istore_1
         2: iload_1
         3: iconst_3
         4: iload_1
         5: iinc          1, 1
         8: imul //iadd: 将操作數棧中的前兩個int值出棧并相乘,然後将相乘的結果放入操作數棧
         9: iadd //iadd: 将操作數棧中的前兩個int值出棧并相加,然後将相加的結果放入操作數棧
        10: istore_2
        11: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: iload_2
        15: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
        18: return
           

可以看出,是從左向右,先對左邊的a原來的值進行了入棧

從位元組碼的角度徹底分析 java i++和++i(附例題)1.第一個問題2.從位元組碼的角度分析:3.關于效率4.例題分析參考網址

例4

int i = 1;
int j = 1;
int k = i++ + ++i + ++j + j++; 
System.out.println(k);
//8
           

表達式原則說多個表達式的加減法運算都是從左到右進行的,這裡的表達式有:i++、++i、++j、j++,都是加法,那我們就從左到右計算這4個表達式就OK了,如下:

1、先算i++,i++之後i的值為2,并傳回++之前的值1,是以整個表達式可以變為:

   1 + ++i + ++j + j++; // 此時的i值為2

2、再計算++i,++i之後i的值為3,并傳回3,是以整個表達式可以變為:

   1 + 3 + ++j + j++; // 此時i的值為3

3、再計算++j,++j之後j的值為2,并傳回2,是以整個表達式可以變為:

   1 + 3 + 2 + j++; // 此時j的值為2

4、再計算j++,j++之後 j的值為3,并傳回2,是以整個表達式可以變為:

   1 + 3 + 2 +2; // 結果為8,此時j的值為3

例5

int a = 0;
int b = 0;
a = a++;//a=0
b = a++;
System.out.println("a = " + a + ", b = " + b);
//a = 1, b = 0
           

a = a++; a++之後a的值為1,并傳回0,是以a的值由1又變回了0

b = a++; a++之後a的值為1,并傳回0,0指派給b,是以b為0,而a是1

例6

int a = 0;
for (int i = 0; i < 99; i++) {
    a = a ++;
}
System.out.println(a);
//0
           

例7

int b = 0;
for (int i = 0; i < 99; i++) {
    b = ++ b;
}
System.out.println(b);//99
           

例8

Integer a = 0;
int b = 0;
for (int i = 0; i < 99; i++) {
    a = a ++;
    b = a ++;
}
System.out.println(a);
System.out.println(b);
//a=99,b=98
           

參考網址

https://blog.csdn.net/android_cai_niao/article/details/106027313

java位元組碼閱讀:

https://juejin.im/post/6844904019941425165