1. 程式人生 > >jdk原始碼淺讀-Integer

jdk原始碼淺讀-Integer

public final class Integer extends Number implements Comparable<Integer>

  Integer 由final修飾了,所以該類不能夠被繼承,同時 Integer 繼承了Number類,因此可以將Integer轉換成 int 、double、float、long、byte和short型別的資料,另外,也實現了comparable介面,因此Integer類也可以進行自然排序。

  構造方法只有兩個:

public Integer(int value) {
        this.value = value;
    }
public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }

  我們主要看第二個構造方法,傳入一個字串,然後呼叫parseInt方法,接下來進入parseInt的原始碼:

  

public static int parseInt(String s, int radix)
                throws NumberFormatException
    {
        /*
         * WARNING: This method may be invoked early during VM initialization
         * before IntegerCache is initialized. Care must be taken to not use
         * the valueOf method.
         
*/ if (s == null) { throw new NumberFormatException("null"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if
(radix > Character.MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; // //是否為負數 boolean negative = false; int i = 0, len = s.length(); //這裡加個負號是防止資料溢位,int的數值範圍 -2的31次方到2的31次方減一 int limit = -Integer.MAX_VALUE; //最小基數 int multmin; //十進位制數字 int digit; if (len > 0) { char firstChar = s.charAt(0); //第一個字元小於0,有可能是"-","+"或其他字元 if (firstChar < '0') { // Possible leading "+" or "-" //為負數 if (firstChar == '-') { negative = true; limit = Integer.MIN_VALUE; } else if (firstChar != '+')//非數字 throw NumberFormatException.forInputString(s); if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s); i++; } /** * 最小基數,主要防止 result *= radix; 這個操作時資料過大 * 導致資料丟失的問題,因為所以帶符號32位int型別整數為-2147483648~2147483647 */ multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE //轉換十進位制,這裡獲取的是radix進位制下相對應的10進位制數字,如: //Character.digit('a',16),則返回 10; //若輸入字元不在進位制的範圍之內,則返回 -1: //Character.digit('t',16),返回 -1,Character.digit('a',10),返回 -1 digit = Character.digit(s.charAt(i++),radix); //返回-1說明字元非法 if (digit < 0) { throw NumberFormatException.forInputString(s); } //超過了資料範圍 if (result < multmin) { throw NumberFormatException.forInputString(s); } /** *在轉換時從高位向地位方向轉換 */ result *= radix; if (result < limit + digit) { throw NumberFormatException.forInputString(s); } result -= digit; } } else { throw NumberFormatException.forInputString(s); } return negative ? result : -result; }

  這個方法中最核心的步驟是1、result *= radix; 2、result -= digit; 經過這兩個步驟將字串轉換成數值型別。大概流程是這樣的:

  假如字串"1234" 轉換成int型別,result 的初始值為0,radix預設為10;

  首先擷取字串的第一個字元1(這裡忽略各種檢查),經過第一步計算 result = 0*10 = 0;第二部計算 result = 0 - 1 = -1;

  第一遍迴圈結束後,result 的值 變成了 -1

  擷取第二個字元 2 ,result = -1 * 10 = -10,result = -10 - 2 = -12;

  擷取第三個字元 3 ,result = -12 * 10 = -120,result = -120 - 3 = -123;

  擷取第四個字元 4 ,result = -123 * 10 = -1230 ,result = -1230-4 = -1234;

   迴圈結束,此時result的值為 -1234,完成字串向整數型的轉換,返回是取反即可。

 

  下面我將從一個面試題引出問題,然後通過閱讀原始碼來解決這個問題。

public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        Integer i5 = Integer.valueOf(100);
        Integer i6 = Integer.valueOf(100);
        Integer i7 = new Integer(100);
        System.out.println("i1 == i2 的結果是:" + (i1 == i2));
        System.out.println("i3 == i4 的結果是:" + (i3 == i4));
        System.out.println("i5 == i6 的結果是:" + (i5 == i6));
        System.out.println("i1 == i5 的結果是:" + (i1 == i5));
        System.out.println("i1 == i7 的結果是:" + (i1 == i7));
    }

執行結果為:

i1 == i2 的結果是:true
i3 == i4 的結果是:false
i5 == i6 的結果是:true
i1 == i5 的結果是:true
i1 == i7 的結果是:false

  我們先來看第一和第二條結果,同樣是比較兩個相同數值,為什麼會有不同的結果呢?接下我將通過原始碼來解釋原因。

  首先,我們通過編譯獲取到class檔案的位元組碼

  從圖中我們可以看到,在執行 Integer i1 = 100 這條命令的時候,編譯器會呼叫Integer中的靜態方法 valueOf,接下來我們看看 valueOf方法是怎麼實現的吧。

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

  這個程式碼看起來很簡單,Integer 中有一個靜態內部類 IntegerCache,呼叫該方法時首先會判斷該值是否在快取的範圍內,如果在則直接將快取中的數值返回,否則返回一個新物件。看到這裡我們似乎已經知道了上面的問題的答案了,接下來繼續看靜態內部類吧

 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            //快取範圍最小(也是預設範圍)為 (-128)~ 127,如果配置java.lang.Integer.IntegerCache.high
            //high 的值可從配置檔案中讀取
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    //獲取配置檔案和127之間的最大值
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    //最大值範圍
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            //建立快取陣列
            cache = new Integer[(high - low) + 1];
            int j = low;
            //將數字快取起來預設 -128 ~ 127
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

  我們知道內部類只有在所在類例項化時才會被例項化,而且只會例項化一次,快取操作是在靜態程式碼塊中完成,也就是說在類被例項化的時候資料就已經被快取好了,接下使用的時候可以直接使用快取的資料。

  現在我們迴歸到上面的問題,結果1中兩個資料均為 100,在快取的範圍中,因此i1和i2都指向的是同一個記憶體地址,因此返回true。結果2中 兩個數都是200,超出了快取的範圍,所以直接new 出了兩個物件,因此他們的記憶體地址不一致,返回結果為false;另外,使用valueOf 和 使用 = 操作符賦值時一樣的,所以結果3和結果4返回結果為 true,結果5中 是直接使用new關鍵字建立物件,所以他們的記憶體地址肯定不一致,結果為false。

  那麼,現在問題又來了,那我怎麼判斷兩個整數的大小呢?繼續看原始碼

 
 
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
public int intValue() {
return value;
}
 

  是的,沒錯,比較兩個數值大小時可以使用equals方法來比較,原始碼中value的型別為 int型,intValue返回的也是value,因此可以判斷兩個數的大小。

public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = 200;
        System.out.println("i1 == i2 的結果是:" + i1.equals(i2)); //true
    }

  補充:equals 與 == 的區別:

  equals 比較的是兩個數值的大小,== 有兩種情況,如果比較的是 基本資料型別,則 == 跟equals一樣都是比較的大小,如果是引用型別或陣列,則比較是記憶體地址。

 

  getChars方法:

static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

        // Generate two digits per iteration
        //每次迴圈獲取後兩位數
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            //使用位移運算的效率高於乘法運算,r為後兩位數
            r = i - ((q << 6) + (q << 5) + (q << 2));
            i = q;
            //獲取後兩位數的個位
            buf [--charPos] = DigitOnes[r];
            //十位
            buf [--charPos] = DigitTens[r];
        }

        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        //每次只取個位數
        for (;;) {
            //相當於i*(52429/524288)=i*0.10000038146972656=i*0.1=i/10
            //這裡選 52429 和 2的19次方相除,得到的結果精度更加高,更加接近於 i/10的結果
            //之所以要這樣轉換,是因為在計算機運算中位移的效率 > 乘法效率 > 除法效率
            q = (i * 52429) >>> (16+3);
            r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
            buf [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
            buf [--charPos] = sign;
        }
    }