入門篇-其之六-附錄一-以Java位元組碼的角度分析i++和++i

来源:https://www.cnblogs.com/iCode504/archive/2023/10/23/17783759.html
-Advertisement-
Play Games

為何強大 記錄全面: 包含請求路徑、請求方法、客戶端IP、設備標識、荷載數據、文件上傳、請求頭、業務邏輯處理時間、業務邏輯所耗記憶體、用戶id、以及響應數據。 配置簡單: 預設不需要寫任何邏輯可開箱即用,靠前4個方法,就可指定某些url不記錄日誌,或不記錄某些請求頭,不記錄某些荷載數據,或決定是否返回 ...


前言:眾所周知,i++++i的區別是:i++先將i的值賦值給變數,再將i的值自增1;而++i則是先將i的值自增1,再將結果賦值給變數。因此,二者最終都給i自增了1,只是方式不同而已。

當然,如果在面試過程中面試官問你這個問題,只回答出上述內容,只能說明你對這方面的知識瞭解的還是太淺顯。那麼i++++i到底有什麼不同之處呢?

一、局部變數表與操作數棧簡介

《深入理解Java虛擬機》第八章對棧幀結構有如下描述Java虛擬機以方法作為最基本的執行單元,“棧幀”(Stack Frame)則是用於支持虛擬機進行方法調用和方法執行背後的數據結構,它也是虛擬機運行時數據區中的虛擬機棧的棧元素。

在一個活動線程中,可能會執行多個方法,因此會存在多個棧幀,和“棧”(先進後出)一樣,處於棧頂的棧幀才是真正運行的,處於棧頂的棧幀稱作“當前棧幀”(Current Stack Frame),這個棧幀所屬的方法稱作“當前方法”(Current Method)。

在執行main方法時,main方法所屬的線程主線程,假設在主線程中調用了一個method1()方法,在method1()內部調用了method2()方法,在method2()方法執行兩個整數運算,示例如下:

/**
 * 方法調用
 *
 * @author iCode504
 * @date 2023-10-23 22:05
 */
public class StackFrameDemo1 {
    public static void main(String[] args) {
        System.out.println("main開始執行");
        method1();
        System.out.println("main執行完成");
    }

    private static void method1() {
        System.out.println("method1開始執行");
        int result = method2();
        System.out.println("result = " + result);
        System.out.println("method1執行結束");
    }

    private static int method2() {
        int var1 = 10;
        int var2 = 20;
        return var1 + var2;
    }
}

運行結果:

image-20231023221121858

由代碼我們可以看出,main方法最先執行一個輸出,然後進入method1執行第一個輸出,再完整執行method2method2執行完成以後,再執行method1,最後執行main方法,由於這段代碼中只涉及一個主線程,並且最先完整執行方法的是method2,因此method2對應的棧幀就是當前棧幀,main方法最後執行完畢,因此main方法對應的棧幀在method2method1之下。以下是這段代碼對應的棧幀概念圖:

image-20231023222236855

在每一個棧幀中存儲了方法的局部變數表、操作數棧、動態鏈接和方法返回地址等信息

1.1 局部變數表

局部變數表(Local variable Table)是一組變數值的存儲空間,用於存放方法參數和方法內部定義的局部變數。

局部變數表的容量是以變數槽(Variable Slot)為最小單位,每個變數槽能存儲基本數據類型和引用數據類型的數據。為了儘可能節省棧幀消耗的記憶體空間,局部變數表中的變數槽是可以重用的。

JVM使用索引定位的方式使用索引變數表,索引值的範圍是從0開始到局部變數表最大變數槽的數量(類似數組結構)。

當一個方法被調用的時候,JVM會使用局部變數表來完成參數值到參數變數列表的傳遞,即實參到形參的傳遞。

1.2 操作數棧

操作數棧(Operand Stack)也稱作操作數棧,它是一個棧結構(後進先出,例如手槍的彈夾,先打出去的子彈是最頂上的子彈)。

在方法開始執行的時候,這個方法對應的操作數棧是空的,在方法執行過程中,會有各種位元組碼指令向操作數棧中寫入或讀取內容,即出棧和入棧操作,例如:兩數相加運算時,就需要將兩個數壓入棧頂後調用運算指令。

操作數棧中的元素的數據類型必須和位元組碼指令序列嚴格匹配,在編譯程式代碼的時候編譯器必須要嚴格保證這一點,在類的校驗階段的數據流分析時候還需要再次校驗。例如:執行加法iaddiint類型,add是兩個數相加)命令時,就需要保證兩個操作數必須是int類型,不能出現其他類型相加的情況。

二、位元組碼分析(圖解)

我們可以從位元組碼的角度進一步對i++++i的執行過程做進一步的分析。以下麵代碼為例:

/**
 * i++和++i的深入分析
 * 
 * @author iCode504
 * @date 2023-10-17 5:58
 */
public class IncrementAndDecrementOperators2 {
    public static void main(String[] args) {
        int intValue1 = 2;
        int intValue2 = 2;
        int result1 = intValue1++;
        int result2 = ++intValue2;
        System.out.println("result1 = " + result1);
        System.out.println("result2 = " + result2);
    }
}

我們需要查看編譯後的位元組碼文件,位元組碼文件不能直接使用記事本打開,但是我們可以使用javap -verbose 文件名.class命令,以IncrementAndDecrementOperators2.class為例:

javap -verbose IncrementAndDecrementOperators2.class

此時就會打開所有的位元組碼文件,我們只需要關註main方法內的執行過程即可:

image-20231017063620627

首先來解釋一下這四行代碼的含義:

0: iconst_2
1: istore_1
2: iconst_2
3: istore_2
  • iconst_2一共有兩部分組成,i指的是int類型(源代碼中我們定義的確實是int類型),const代表常量(數字2是整型常量),iconst_2的含義是將2入操作數棧。
  • istore_1中的store代表的是存儲,istore_1的含義是將操作數棧中的數值2出棧,存入到局部變數表1的位置。同理,i_store2表示將操作數棧中的數值2出棧,存儲到局部變數表2的位置。

以下是前面四行代碼存儲過程圖(存儲過程全部流程圖點擊此鏈接下載:點我下載):

image-20231023180543810

image-20231023180636090

image-20231023180949754

image-20231023181650491

此時我們繼續觀察4-8行代碼:

4: iload_1
5: iinc			1, 1
8: istore_3
  • iload_1的作用是將局部變數表1號位置存儲的值移動到操作數棧的棧頂。
  • 第5行的iinc有兩個參數,第一個參數1是局部變數表的位置,另一個參數1的含義是在該位置存儲一個1,如果這個位置存在值,那麼這個值的結果是已存在值 + 參數值
  • istore_3將操作數棧中的數移動到局部變數表的3號位置。

以下是這三行代碼的示意圖:

image-20231023182309249

image-20231023182828787

image-20231023183808061

9-12行的位元組碼的作用原理和4-8行的作用原理基本相同:

9: iinc			2, 1
12: iload_2
13: istore		4

istore 4的作用是將操作數棧中的值存儲到局部變數表4號位置。

以下是這三行代碼的示意圖:

image-20231023183842487

image-20231023184139208

image-20231023184445793

接下來15-30行是和系統輸出有關的。其中第30行iload_3在局部變數表中(這個值為2)值移動到操作數棧頂供系統輸出,事實上iload_3的值正好對應源代碼中變數result1的值。也就是說,result1輸出結果就是iload_3的數值2。

image-20231021153508365

同理,iload 4就是第二個要輸出的值,在局部變數表中第4個位置存儲的值正好是3,而輸出的變數名是result2,因此result2的輸出結果是3。

image-20231021154150053

三、i++++i性能分析

i++++i主要用在普通for迴圈上,那麼我們就將二者用在for迴圈上,迴圈相同的次數,從位元組碼的角度進行分析。

以下是使用i++++i的兩個for迴圈文件:

/**
 * i++在for迴圈的使用
 *
 * @author ZhaoCong
 * @date 2023-10-21 16:14:33
 */
public class LoopTest1 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {

        }
    }
}
/**
 * ++i在for迴圈的使用
 *
 * @author ZhaoCong
 * @date 2023-10-21 16:15:17
 */
public class LoopTest2 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; ++i) {

        }
    }
}

執行編譯命令以後,我們來查看兩個文件的位元組碼:

image-20231021162911742

image-20231021162952984

仔細觀察這兩個位元組碼文件內容,我們發現在兩個文件main方法的位元組碼內容完全相同。由此可見,兩種方式執行for迴圈的效率是相同的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:WangMin 格言:努力做好自己喜歡的每一件事 作為前端開發來說,要掌握的CSS基礎一定很多,那麼CSS中盒子模型肯定是必考必問必掌握的前端知識點,因為它是CSS基礎中非常重要的內容,接下來我們就一起來瞭解一下盒子模型吧! 什麼是盒子模型? CSS 所有的HTML 標簽元素在網頁中都生成了一 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 頁面效果 具體實現 新增 1、監聽滑鼠抬起事件,通過window.getSelection()方法獲取滑鼠用戶選擇的文本範圍或游標的當前位置。 2、通過 選中的文字長度是否大於0或window.getSelection().isColla ...
  • 開發過程中經常遇到支付寶小程式跳轉的問題,這裡總結一下支付寶小程式跳轉的常見場景和方式,希望可以對大家有所幫助。 話不多說,上乾貨! 支付寶小程式跳轉的三種行為 支付寶小程式跳轉可以拆分為三種行為,即: 外部跳轉支付寶小程式 支付寶小程式內部頁面之間跳轉 支付寶小程式內部跳轉到外部 一、外部跳轉小程 ...
  • 飛碼是京東科技研發的低代碼產品,可使營銷運營域下web頁面快速搭建。飛碼是單web頁面搭建工具,從創建頁面到監測再到投產的一站式解決方案 ...
  • 接上一節:從零用VitePress搭建博客教程(5) - 如何自定義頁面模板、給頁面添加獨有的className和使頁面標題變成側邊目錄? 九、第三方組件庫的使用 我們經常看見UI組件庫的文檔,這裡我們就用element-plus第三方組件庫為例子,搭建組件庫文檔 examples:作為組件庫示例目 ...
  • Hope is a good thing, maybe the best of things, and no good thing ever dies. 希望是件美麗的東西,也許是最好的東西,而美好的東西是永遠不會消逝的。 大家好,我是勇哥 。 1024 , 程式員節,圓了我一個小小的夢。 花了半年 ...
  • 1 我的高價位同事 我有個互聯網前同事快40了,之前35k,裁員後找了很久工作。最後18k入職一家公司繼續乾。只要降低預期就行了。8k不行就4k,4k不行就1k。那樣了是不是還不如開滴滴、送外賣、做物流。那樣多數人會選擇開滴滴去單的。熬吧,人口今年開始負增長了。捲王,工賊,潤的潤,捲的捲,都是個人選 ...
  • JUC前置知識 JUC概述 在開發語言中,線程部分是重點,JUC是關於線程的。JUC是java.util.concurrent工具包的簡稱。這是一個處理線程的工具包,JDK1.5開始出現的。 線程和進程 線程和進程的概念 進程(process): 是電腦的程式關於某數據集合上的一次允許活動,是操作 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...