JDK源碼分析之Integer

常用數據類型大家都會用,JDK也爲了保持一切皆對象的原則,將8大常用數據類型都封裝成了對應的對象,本次先來分析JDK怎麼封裝的Integer。

首先看Integer的繼承關係

public final class Integer extends Number implements Comparable<Integer> {
是繼承Number實現Comparable的,所以Integer也能用compareTo方法進行比較。

Integer 有一個私有屬性 value 

 /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;
可以看出這個value是一個final型的,這也解釋了爲什麼我們new 兩個同樣數字的Integer對象爲什麼會不是一個對象,當然,也有特殊情況,==比較時會出現相同對象,這是因爲Integer類內會給緩存一部分數據,這在下面再具體分析。
接着我們看Integer的構造方法:

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

可以發現這兩個構造方法是比較簡單的,第一個是調用的parseInt方法轉化成了Integer對象,關於parseInt方法稍會介紹。

下面分析下Integer是怎麼緩存一部分數據的,這就要歸功於Integer內部類IntegerCache了。

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

        static {
            // 默認緩存的最大值是127
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");//可以外部配置
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);  //取配置的和127的大值
                    // 
                    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;
            for(int k = 0; k < cache.length; k++)   //初始化cache的數據
                cache[k] = new Integer(j++);

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

        private IntegerCache() {}
    }


代碼裏面有註釋,還是比較好理解的,分析==比較之前先說下,自動拆箱自動裝箱。比如Integer i= 23;JDK會自動將23給我們裝箱成Integer對象,當然這是JDK5之後纔有的,debug可以看到JDK給我們裝箱時是調用的valueOf方法。下面看下這個方法。


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


這個方法可以很好理解,JDK在裝箱前會先判斷i是否在緩存cache中,如果在的話就直接拿緩存中的數據,如果不在的話才new一個對象。所以當我們這樣寫Integer i = 23;

Integer m = 23;  i== m ,會返回TRUE,這個纔是根本原因。

下面說下parseInt(String str);這個方法


public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
}


發現調用的是parseInt(String s,int radix);這個方法


public static int parseInt(String s, int radix) throws NumberFormatException{
        //對輸入的字符進行判斷非空或者是否大於最大或者小於最小,如果是的話,拋異常
        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 limit = -Integer.MAX_VALUE;
        int multmin;
        int digit;

        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // 如果firstChar < '0'  可能會導致 第一個是'+'或者'-'
                if (firstChar == '-') {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+')  //如果也不是'+'的話,拋異常
                    throw NumberFormatException.forInputString(s);

                if (len == 1) // 不能僅有符號
                    throw NumberFormatException.forInputString(s);
                i++;
            }
            multmin = limit / radix;溢出判斷技巧 ,可在乘法之前判斷乘法後是否溢出
            while (i < len) {
                // 這個方法可以先簡單的理解成將string型的轉換成int型的稍後介紹這個方法
                digit = Character.digit(s.charAt(i++),radix);
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix; //先乘一個radix
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;  //再減一個digit得到對應基數的數字
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        return negative ? result : -result;  //最終result是一個相對應基數的int 型的數
    }


發現上個方法主要是Character.digit(char ch,int radix);這個方法下面我們看一下,這個方法,


 public static int digit(char ch, int radix) {
        return digit((int)ch, radix);  //調用Character類的digit(int codePoint, int radix);方法
 }
public static int digit(int codePoint, int radix) {
        return CharacterData.of(codePoint).digit(codePoint, radix);//調用CharacterData.of(int codePoint).digjt(int codePoint,int radix);
}

我們查看下CharacterData的of方法:

static final CharacterData of(int ch) {
        if (ch >>> 8 == 0) {     // 邏輯右移,根據值的大小選擇不同的實例此處我們看下第一個
            return CharacterDataLatin1.instance;
        } else {
            switch(ch >>> 16) {  //plane 00-16
            case(0):
                return CharacterData00.instance;
            case(1):
                return CharacterData01.instance;
            case(2):
                return CharacterData02.instance;
            case(14):
                return CharacterData0E.instance;
            case(15):   // Private Use
            case(16):   // Private Use
                return CharacterDataPrivateUse.instance;
            default:
                return CharacterDataUndefined.instance;
            }
        }
    }


CharacterDataLatin1.java部分源碼


int digit(int ch, int radix) {
        int value = -1;
        if (radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX) {  //判斷基數是否在最大和最小範圍內
            int val = getProperties(ch);  //調用此方法 此方法是得到字符的ASCII值
            int kind = val & 0x1F;  //value & 11111  這裏與過後只可能會是後面的幾種  2: 小寫字符 1:大寫字符 9:數字 20~30 標點 12空格
            if (kind == Character.DECIMAL_DIGIT_NUMBER) {  //kind == 9  數字
                value = ch + ((val & 0x3E0) >> 5) & 0x1F;  //value = ch + ((val & 1111100000) >> 5 ) & 11111  關於這一塊,本人也是正在學習中,目前沒太明白
            }
            else if ((val & 0xC00) == 0x00000C00) {
                // Java supradecimal digit
                value = (ch + ((val & 0x3E0) >> 5) & 0x1F) + 10;
            }
        }
        return (value < radix) ? value : -1;
    }
int getProperties(int ch) {
        char offset = (char)ch;
        int props = A[offset];  //將A數組進行
        return props;
}

截止到現在parseInt(String s);方法應該是完了。感覺JDK封裝的挺厲害的,還是應該看源碼學習比較好。下面看Integer.toString(int i);方法。

public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }


此處判斷數字的size時感覺非常好。我們看下stringSize的方法。直接就可以得到int類型的數有幾位


 final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                      99999999, 999999999, Integer.MAX_VALUE };

    // Requires positive x
    static int stringSize(int x) {
        for (int i=0; ; i++)
            if (x <= sizeTable[i])
                return i+1;
    }
static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

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

        // 每次生成兩個
        while (i >= 65536) {  
            q = i / 100;
        // really: r = i - (q * 100);
            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 (;;) {
            q = (i * 52429) >>> (16+3); //這裏用52429 好像是因爲這個數進行乘除精確度最高
            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;
        }
    }


今天就先到這裏吧!!!有時間繼續。。。




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