【程序員】你知道 a小於b 和 a 減 b 小於 0 的區別嗎?

2020年1月1號全面小康社會已經來了,2020年了第一批90後也已經30歲了。在此元旦,新的一年託尼祝大家代碼永無bug,新的一年升職且加薪。
好了,言歸正傳,其實我們在編碼的過程中,有的時候真是不是特意寫bug的,我們也是想好好寫代碼的。
只不過有的時候不熟悉源碼,不小心就踩入坑中,而且摔個底朝天,程序員真的好難🤯。

序幕

上代碼if (a < b)if (a - b < 0)?這不就是兩個參數的比較嗎?難道還有什麼詭異之處?應該是一樣的呀。

你覺得上面代碼會打印什麼?先別看下面的內容,第一感覺是什麼樣??

字節碼

從字面理解就是比較a和b的大小,按道理執行速度、字面意思都一樣。其實機器是死的,它不會按照人類的理解去比較的。

有的時候我們需要通過字節碼去思考到底程序是怎麼執行的?

// access flags 0x1
 public test()V
 @Lorg/junit/Test;()
  L0
   LINENUMBER 22 L0
   LDC 2147483647
   ISTORE 1
  L1
   LINENUMBER 23 L1
   LDC -2147483648
   ISTORE 2
  L2
   LINENUMBER 24 L2
   ILOAD 1
   ILOAD 2
   IF_ICMPGE L3
  L4
   LINENUMBER 25 L4
   GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
   LDC "a < b"
   INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
  L3
   LINENUMBER 27 L3
  FRAME APPEND [I I]
   ILOAD 1
   ILOAD 2
   ISUB
   IFGE L5
  L6
   LINENUMBER 28 L6
   GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
   LDC "a - b < 0"
   INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
  L5
   LINENUMBER 30 L5
  FRAME SAME
   RETURN
  L7
託尼帶着大家讀字節碼。其實字節碼很簡單的,看上面截圖紅色框。挑選你熟悉的看LINENUMBER、LDC、ISTORE、IF_ICMPGE、ISUB
  • LINENUMBER 代表的行號
  • LDC 代表JVM 採用 LDC 指令將常量壓入棧中
  • ISTORE 將一個數值從操作數棧存儲到局部變量表,還有``istore、istore_<n>、lstore、lstore_<n>等等。
  • IF_ICMPGE 比較棧頂兩int型數值大小,當結果大於等於0時跳轉
  • ISUB 將棧頂兩int型數值相減並將結果壓入棧頂

更多指令參考

以上大概講了下JVM的指令,希望對你有點幫助。

再談溢出感知代碼

 @Test
    public void test() {
        int a = Integer.MAX_VALUE;
        int b = Integer.MIN_VALUE;
        if (a < b) {
            System.out.println("a < b");
        }
        if (a - b < 0) {
            System.out.println("a - b < 0");
        }
    }

這段代碼只能輸出 a-b<0 ,然後a<b 是不會打印出來的。

在JDK源碼中爲什麼ArrayList 中用if (a - b < 0)而不用if (a < b)?這個涉及到溢出感知代碼。

我們來分析List 如何擴容的grow 方法?

/**
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    modCount++;

    // Overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // Overflow-conscious code
    // /把當前數組的長度賦給oldCapacity
    int oldCapacity = elementData.length;
    // 新的數組容量=老的數組長度的1.5倍,oldCapacity >> 1 相當於除以2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新的數組長度小於傳入的參數,那麼當前新的
    // 數組的長度則爲傳入進來的長度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 新的數組的長度和 數組中最大值也就是(ArrayList 的數組最大值 Integer.MAX_VALUE - 8)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 傳入的舊的數組容量如果大於數組中最大默認值
    // 則取最大的默認值
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

上面的註釋也加了,下面來說重點。 oldCapacity 這個值如果是非常接近Integer.MAX,那麼執行這一句話
int newCapacity = oldCapacity + (oldCapacity >> 1);,那麼此刻newCapacity 是一個負數 ,如果JDK的代碼是這麼判斷大小
if (newCapacity<minCapacity)這種寫法,那麼這個邏輯判斷條件永遠是TRUE,顯然和期望是相反的。

溢出

以下代碼是ArrayList 到數組擴容源碼, 我們來好好分析何爲溢出感知代碼?英文叫做 overflow-conscious code。
在計算機中當整型變量的值爲 Integer.MAX_VALUE(2147483647)時,繼續累加一個正的整型值,就會變成一個負數,這種情況稱之爲上溢。
當整型變量的值爲 Integer.MIN_VALUE(-2147483648) 時,繼續累加一個負的整型值,就會變成一個非負數,這種情況稱之爲下溢。
好比水缸就能只能盛這麼多水,你繼續添加水,水就會自然而然到溢出來了。

結論

在讀源碼過程中的我們要考慮到代碼的健壯性,邏輯到衍生性。希望這篇博文能讓你學到一點東西,哪怕一點點,我就覺得挺開心的。

衍生

在AarryList 的源碼中private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;,爲什麼要再減去 8 呢?

/**
    * The maximum size of array to allocate.
    * Some VMs reserve some header words in an array.
    * Attempts to allocate larger arrays may result in
    * OutOfMemoryError: Requested array size exceeds VM limit
    */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如上所訴,有些JVM保留頭部信息怕溢出了,所以設置了減8 。這也是JVM向上兼容的一種方式。

謝謝你們看到了這裏,感謝🙏。新的一年祝升職加薪。
在這裏插入圖片描述
關注公衆號回覆視頻、書籍關鍵字更多免費資料等着你。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章