走進 JDK 之 Integer

文中相關源碼:html

Integer.javajava



開發的越久,越能體會到基礎知識的重要性。抽空捋一下 JDK 源碼,權當查漏補缺。讀完以後,你會發現 JDK 源碼真的會給你不少驚喜。git

Integer 是基本類型 int 的包裝類,它提供了一些處理 int 數值的方法,Stringint 相互轉換的方法。另外,它還提供了一些位運算,這些位運算來自於 Henry S. Warren Jr.《Hacker's Delight》github

類聲明

首先看一下 Integer 的類聲明:面試

public final class Integer extends Number implements Comparable<Integer>{}
複製代碼

Inetger 是不可變類,沒法被繼承。關於 不可變類 的詳細介紹,能夠閱讀我以前的一篇文章 《String 爲何不可變?》數組

Integer 繼承了抽象類 Number,並實現了它的下列方法: byteValue() shortValue() intValue() longValue() floatValue() doubleValue(),將 int 轉換爲其餘基本類型的值,實現方法都是強轉。緩存

Integer 還實現了 Comparable 接口,所以也具有了比較對象大小的能力,其 compareTo() 方法具體實現以下:bash

public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
複製代碼

字段

private final int value; // Integer 類包裝的值,真正用來存儲 int 值

public static final int   MIN_VALUE = 0x80000000; // int 最小值爲 -2^31

public static final int   MAX_VALUE = 0x7fffffff; // int 最大值爲 2^31-1

public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); // 基本類型 int 包裝類的實例

public static final int SIZE = 32; // 以二進制補碼形式表示 int 值所需的比特數

public static final int BYTES = SIZE / Byte.SIZE; // 以二進制補碼形式表示 int 值所需的字節數。1.8 新添加字段

private static final long serialVersionUID = 1360826667806852920L; // 序列化
複製代碼

Integer 只有一個非靜態字段 value,用來表示其包裝的 int 值。0x800000000x7fffffff 分別是 int 最小值和最大值的十六進制表示,這裏要注意十六進制 int 值在內存中的表示方法,有興趣的同窗能夠了解一下,這裏先佔個坑,有時間單獨寫一篇文章微信

咱們都知道 int 是 4 字節,32 比特,和 C/C++ 不一樣的時,Java 中整型的取值範圍和運行 Java 代碼的機器是無關的。不管是 16 位系統,32 位系統,仍是 64 位系統,int 永遠都是 4字節。這也體現了 Java 的 「一次編寫,處處運行」。less

構造函數

Integer 有兩個構造函數。第一個以下所示:

public Integer(int value) {
    this.value = value;
}
複製代碼

直接傳入基本類型數值,賦值給 value 字段。再看一下第二個構造函數:

public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}
複製代碼

參數是一個字符串,經過 parseInt(String s,int radix) 轉換爲 int 值,再賦給 value 字段。

Stringint,舉幾個例子仍是很容易理解的:

"1234" -> 123

"-5678" -> 5678

"ff" -> 255

根據進制的不一樣,Integer 類中列舉了全部可能用來表示數字的字符:

final static char[] digits = {
    '0' , '1' , '2' , '3' , '4' , '5' ,
    '6' , '7' , '8' , '9' , 'a' , 'b' ,
    'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
    'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
    'o' , 'p' , 'q' , 'r' , 's' , 't' ,
    'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
複製代碼

下面分析 parseInt() 函數的具體實現。

方法

parseInt(String,int) / parseInt(String)

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}
複製代碼
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) { // 進制最小值是 2
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }

    if (radix > Character.MAX_RADIX) { // 進制最大值是 36
        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);
        // '0' == 48, 48 如下都是非數字和字母
        // '+' == 43, '-' == 45
        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++;
        }
        multmin = limit / radix;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            // 將 char 轉換爲相應進制的 int 值
            digit = Character.digit(s.charAt(i++),radix);
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            /* * multmin = limit / radix, * 若是這裏 result > multmin , 下一步 result *= radix 就會溢出 */
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            // 也是溢出檢查,例如 parseInt("2147483648",10) 就沒法經過此檢查
            // 2147483648 == Integer.MAX_VALUE + 1
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            result -= digit; // 這裏採用負數相減的形式,而不是使用正數累加,防止溢出
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    return negative ? result : -result;
}
複製代碼

代碼挺長,其實邏輯很簡單。以 parseInt("1234",10) 爲例:

1234 = (((1*10)+2)*10+3)*10+4
複製代碼

實際上並非這樣循環累加的,而是用負數累減的形式。由於 int 最大值爲 2^31-1,最小值爲 -2^31,採用正數累加的方式可能會致使溢出。parseInt() 中作了兩次溢出檢查,一旦溢出直接拋出異常。

除此以外,還須要注意的一點是進制的取值範圍。最小進製爲 2,最大進製爲 36,不在此範圍內的直接拋出異常。此範圍對應的全部可能表示數字的字符存儲在靜態數組 digits 中:

final static char[] digits = {
    '0' , '1' , '2' , '3' , '4' , '5' ,
    '6' , '7' , '8' , '9' , 'a' , 'b' ,
    'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
    'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
    'o' , 'p' , 'q' , 'r' , 's' , 't' ,
    'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
複製代碼

parseUnsignedInt(String,int) / parseUnsignedInt(String)

Java 中 int 都是有符號類型的,所以 parseInt() 也是針對有符號類型的。Integer 另外提供了 parseUnsignedInt 函數來處理無符號類型。可是歸根結底,Java 根本沒有無符號數,對於大於 Integer.MAX_VALUE 的數值,使用負數來表示,其實也就是溢出了。

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}
複製代碼
public static int parseUnsignedInt(String s, int radix) throws NumberFormatException {
    if (s == null)  {
        throw new NumberFormatException("null");
    }

    int len = s.length();
    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar == '-') { // 無符號數以 - 開頭,直接拋出異常
            throw new
                NumberFormatException(String.format("Illegal leading minus sign " +
                                                   "on unsigned string %s.", s));
        } else {
            /* * 肯定再有符號 int 取值範圍內,直接調用 parseInt() 當作有符號數處理 * 其餘狀況當作 long 值處理,調用 Long.parseLong() */
            if (len <= 5 || // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits
                (radix == 10 && len <= 9) ) { // Integer.MAX_VALUE in base 10 is 10 digits
                return parseInt(s, radix);
            } else {
                long ell = Long.parseLong(s, radix);
                if ((ell & 0xffff_ffff_0000_0000L) == 0) { // 不超過無符號 int 的最大值,直接強轉 int 返回
                    return (int) ell;
                } else { // 超過無符號 int 最大值,拋出異常
                    throw new
                        NumberFormatException(String.format("String value %s exceeds " +
                                                            "range of unsigned int.", s));
                }
            }
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
}
複製代碼

注意一下幾點:

  • - 開頭,直接拋出異常
  • 肯定是有符號類型的,仍調用 parseInt(),不然看作 long 處理

調動 Long.parseLong() 獲得返回值後,須要判斷是否超過無符號 int 的最大值。上面使用的判斷方式是:

if ((ell & 0xffff_ffff_0000_0000L) == 0)
複製代碼

知足此條件則意味着 ell 高八位必爲 0,因此不會超過無符號 int 最大值。

除了 parseInt() 系列,還有幾個 Stringint 的方法也一併分析一下。

decode(String)

public static Integer decode(String nm) throws NumberFormatException {
    int radix = 10;
    int index = 0;
    boolean negative = false;
    Integer result;

    if (nm.length() == 0)
        throw new NumberFormatException("Zero length string");
    char firstChar = nm.charAt(0);
    // Handle sign, if present
    if (firstChar == '-') { // 負數
        negative = true;
        index++;
    } else if (firstChar == '+') // 正數
        index++;

    // Handle radix specifier, if present
    // 以 "0x" "0X" "#" 開頭表示十六進制
    // 以 "0" 開頭表示八進制
    if (nm.startsWith("0x", index) || nm.startsWith("0X", index)) {
        index += 2;
        radix = 16;
    }
    else if (nm.startsWith("#", index)) {
        index ++;
        radix = 16;
    }
    else if (nm.startsWith("0", index) && nm.length() > 1 + index) {
        index ++;
        radix = 8;
    }

    if (nm.startsWith("-", index) || nm.startsWith("+", index))
        throw new NumberFormatException("Sign character in wrong position");

    try {
        result = Integer.valueOf(nm.substring(index), radix);
        result = negative ? Integer.valueOf(-result.intValue()) : result;
    } catch (NumberFormatException e) {
        // If number is Integer.MIN_VALUE, we'll end up here. The next line
        // handles this case, and causes any genuine format error to be
        // rethrown. Integer.MIN_VALUE 會進入此分支
        String constant = negative ? ("-" + nm.substring(index))
                                   : nm.substring(index);
        result = Integer.valueOf(constant, radix);
    }
    return result;
}
複製代碼

將特定的字符串轉換爲 int 值,可接受十進制、十六進制、八進制形式的字符串。其中,以 0x0X# 開頭的字符串表示十六進制,以 0 開頭表示八進制。肯定進制 radix 以後,調用靜態方法 Integer.valueOf(String,int) 方法。接着跟進這一方法。

Integer.valueOf(String,int)

public static Integer valueOf(String s, int radix) throws NumberFormatException {
    return Integer.valueOf(parseInt(s,radix));
}
複製代碼

最終仍是調用了 parseInt() 方法來進行轉化獲得對應的 int 值,並經過 Integer.valueOf(int) 方法獲得包裝類 Integer 對象。接着看看 Integer.valueOf(int) 的具體實現。

Integer.valueOf(int)

再看這個方法以前,先看一道經典的面試題:

Integer b1 = 127;
Integer b2 = 127;

Integer c1 = 128;
Integer c2 = 128;

System.out.println(b1 == b2); // true
System.out.println(c1 == c2); // false
複製代碼

相信你們對打印結果應該沒有什麼疑問。有疑問的話,帶着疑問看源碼。

經過 javap 命令看下上面的代碼究竟是如何執行,部分字節碼以下:

21: bipush        127
23: invokestatic  #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
26: astore_1
27 bipush        127
29: invokestatic  #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
32: astore_2
33: sipush        128
36: invokestatic  #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
39: astore_3
40: sipush        128
43: invokestatic  #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
複製代碼

經過上面的字節碼能夠發現,Java 經過 Integer.valueOf(int) 函數來進行基本數據類型 int 的自動裝箱。Integ.valueOf(int) 源碼以下:

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

邏輯很清晰,在 IntegerCache.LOWIntegerCache.HIGH 之間的數值,直接返回緩存中已經建立好的對象,其他值每次都建立新的對象。IntegerCache.LOW-128IntegerCache.HIGH 默認爲 127,能夠經過設置 -XX:AutoBoxCacheMax=<size> 進行更改。

IntegerCache 的任務很簡單,就是在 VM 加載 Integer 類的時候給緩存數組填充值。具體源碼以下:

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
        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);
                // 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;
        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() {}
}
複製代碼

Stringint 的方法差很少就介紹完了,下面分析 intString 的方法。

toString() / toString(int)

public String toString() {
    return toString(value);
}
複製代碼
public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    // 獲取長度,負數需 +1,表示符號 '-'
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}
複製代碼

這是複寫的 toString() 方法,默認將當前 int 值轉化爲十進制形式的字符串。這段源代碼真的很精髓,開發者爲了提高運行效率無所不用其極,讓人心生敬佩。下面詳細分析一下這個方法:

  1. 對於 Integer.MIN_VALUE ,直接返回 -2147483648
  2. 經過 stringSize() 方法獲取須要的字符串長度 size
  3. 新建字符數組 buf,用來存儲字符串
  4. 經過 getChars() 方法填充字符數組 buf
  5. 經過 String 的構造函數生成字符串

核心函數就是 stringSize()getChars()

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;
}
複製代碼

獲取 int 值對應的十進制字符串的長度,只接收正數。巧妙的使用了一個 sizeTable 數組,循環匹配,能夠很方便的獲取對應的字符串長度。sizeTable 數組最大值爲 Integer.MAX_VALUE,這也就解釋了第一步中遇到 Integer.MIN_VALUE 時直接返回,並不進入 stringSize() 方法。

getChars(int,int,char[])

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 = 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);
        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;
    }
}
複製代碼

getChars() 方法真的是將運行效率優化到了極致。其做用很簡單,就是將數值 i 的每一位數字做爲字符填充到字符數組 buf 中。若是是我來實現的話,多是下面這樣一個版本:

public static void getChars(int i, int index, char[] buf) {
    int q, r;
    for (int n = index - 1; n >= 0; n--) {
        q = i / 10;
        r = i - q * 10;
        i = q;
        buf[n] = digits[r];
    }
}
複製代碼

i 的最低位數字開始,循環取出並塞到 buf 中。對,就是這麼簡單的邏輯,Integer 源碼中還玩出來了這麼多花樣。下面仔細看一下源碼的實現和個人版本有哪些不一樣。

源碼中以 65536 爲界限,分別執行兩個不一樣的循環體。暫且無論這個 65536 從何而來,先看一下這兩個循環體。

// Generate two digits per iteration
// 每次取 i 的最後兩位
while (i >= 65536) {
    q = i / 100;
// really: r = i - (q * 100);
    // r = i - (q * (2^6 + 2^5 + 2^2))
    r = i - ((q << 6) + (q << 5) + (q << 2));
    i = q;
    buf [--charPos] = DigitOnes[r]; // 取餘操做
    buf [--charPos] = DigitTens[r]; // 除法操做
}
複製代碼

不知道它在幹嗎的時候,debug 一下就很清晰了。每次循環取出 i 的最後兩位數字做爲一個 int 值 r,而後分別進行 r/10r%10 分別獲得這兩個數字。源碼中巧妙的使用了兩個數組,避免進行算數運算,看一下這兩個數組:

final static char [] DigitOnes = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    } ;
複製代碼

DigitOnes 數組存儲了 09910 取餘的運算結果。

final static char [] DigitTens = {
    '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
    '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
    '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
    '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
    '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
    '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
    '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
    '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
    '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
    '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
    } ;
複製代碼

DigitTens 數組存儲了 099 除以 10 的運算結果。

還有一個須要注意的地方,看這行代碼:

/* * equals -> r = i - (q * (2^6 + 2^5 + 2^2)) * equals -> r = i - q * 100; */
r = i - ((q << 6) + (q << 5) + (q << 2));
複製代碼

使用移位和加法代替了乘法運算,這也是一個提高運行效率的細節,相信平常開發中你們應該不多能想到。下一個循環中,你也能夠看到相似的操做。在這裏,先提早總結一下:

  • 移位比加減乘除效率高
  • 加減法比乘除法效率高
  • 乘法比除法效率高

大於 65536 的循環體就先說到這,下面看小於 65536 時執行的循環體:

// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
    q = (i * 52429) >>> (16+3); // q = i * 10
    r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
    // 此時 r 爲數字 i 的最後一位
    buf [--charPos] = digits [r];
    i = q;
    if (i == 0) break;
}
複製代碼

每次取出最後一位數字,塞入字符數組 buf 中。有了上一個循環體的經驗,這個循環體的前兩行代碼也很好理解了。

/* * equals -> (i * 52429) / 2^19 * equals -> (i * 52429) / 524288 * equals -> i / 10 */
q = (i * 52429) >>> (16+3);

/* * equals -> i - q * (2^3 + 2) * equals -> i - q * 10 */
r = i - ((q << 3) + (q << 1));
複製代碼

第一行使用乘法和移位代替了除法,第二行使用加法和移位代替了乘法。

看到這裏,你應該還有一些疑問,爲何是 65536?爲何是 52429?這兩個數字的選取有什麼依據嗎?

回過頭再想一下,咱們用 (i * 52429) >>> (16+3) 來代替的是 i / 10,其中的數學關係是 52429 = (2 ^ 19) / 10 +1,假若咱們不用 52429 ,換作其餘的數,也就是改變 19 的值,咱們來列舉一下:

2^10 / 10 + 1 = 103, 103 / 1024 = 0.100585938...
2^11 / 10 + 1 = 205, 205 / 2048 = 0.100097656...
2^12 / 10 + 1 = 410, 410 / 4096 = 0.100097656...
2^13 / 10 + 1 = 820, 820 / 8192 = 0.100097656...
2^14 / 10 + 1 = 1639, 1639 / 16384 = 0.100036621...
2^15 / 10 + 1 = 3277, 3277 / 32768 = 0.100006104...
2^16 / 10 + 1 = 6554, 6554 / 65536 = 0.100006104...
2^17 / 10 + 1 = 13108, 13108 / 131072 = 0.100006104...
2^18 / 10 + 1 = 26215, 26215 / 262144 = 0.100002289...
2^19 / 10 + 1 = 52429, 52429 / 524288 = 0.100000381...
2^20 / 10 + 1 = 104858, 104858 / 1048576 = 0.100000381...
2^21 / 10 + 1 = 209716, 209716 / 2097152 = 0.100000381...
2^22 / 10 + 1 = 419431, 419431 / 4194304 = 0.100000143...
複製代碼

從上面的計算結果能夠看出來,大於等於 19 的時候精度會比較高。假若咱們這裏取 20,即等式爲:

q = (i * 104858) >>> 20
複製代碼

那麼,這時分隔兩個循環的 i 值應該取多少呢?注意這裏是無符號右移,因此 i * 104858 理論上能夠達到無符號 int 的最大值 2^32-1,即 4294967295,分隔值 i 不能大於 (2^32-1) / 104858 = 40659 ,比 65536 小了一些。假若咱們取 21 ,則分隔值 i 不能大於 (2^32-1) / 209716 = 20479, 更小了一些。顯然,選取 19,既保證了精度儘可能的高,又保證了分隔值的取值儘可能的高。(2^32-1) / 52429 = 81919,不超過 81919,從執行效率方面考慮,源碼中就選擇了 65536 這個數字。

綜上,就有個這樣的組合,65536 52429 19。最後還有一個疑問,源碼中並非直接寫 19 的,而是用 16 + 3 代替,這樣也能提升運行效率嗎?

toString(int,int)

上面分析的 toString(int) 方法是指定轉換爲十進制字符串的,咱們還可使用兩個參數的 toString() 方法轉換爲指定進制的字符串。代碼比較簡單,就直接在源碼中註釋。

public static String toString(int i, int radix) {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10; // 不合法的進制統一設置爲 10

    /* Use the faster version * 十進制仍是使用上面分析的方法 */
    if (radix == 10) {
        return toString(i);
    }

    char buf[] = new char[33];
    boolean negative = (i < 0);
    int charPos = 32;

    if (!negative) {
        i = -i;
    }

    // 循環對進制 radix 取餘
    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)];
        i = i / radix;
    }
    buf[charPos] = digits[-i];

    if (negative) {
        buf[--charPos] = '-';
    }

    return new String(buf, charPos, (33 - charPos));
}
複製代碼

toUnsignedString(int)

public static String toUnsignedString(int i, int radix) {
    return Long.toUnsignedString(toUnsignedLong(i), radix);
}
複製代碼

轉成無符號字符串,這裏轉成 long 型再調用 Long.toUnsignedString(),這裏不作過多分析。

getInteger()

最後還要一個 getInteger() 方法,用的不是不少,用於獲取系統屬性指定的 int 值。看一下簡單的例子就明白了:

Properties properties = System.getProperties();
properties.put("luyao","123456");
System.out.println(Integer.getInteger("luyao"));
System.out.println(Integer.getInteger("luyao",0));
System.out.println(Integer.getInteger("luyao",new Integer(0)));
複製代碼

打印結果都是 1234。區別就是,當指定屬性名稱不存在時,後面兩個方法提供了默認值,第一個方法會返回 null

public static Integer getInteger(String nm, Integer val) {
    String v = null;
    try {
        v = System.getProperty(nm);
    } catch (IllegalArgumentException | NullPointerException e) {
    }
    if (v != null) {
        try {
            return Integer.decode(v);
        } catch (NumberFormatException e) {
        }
    }
    return val;
}
複製代碼

能夠看到實際上是調用了 Integer.decode() 方法,前面已經分析過 decode() 方法,這裏就再也不多說了。

Stringint 相互轉換的方法就說到這裏了,大部分方法應該都提到了。最後簡單說說一些位運算。

位運算

類註釋中提到了一本書,Henry S. Warren Jr.《Hacker's Delight》Integer 中的位運算原理在這本書中都有介紹。我在這裏僅僅說明方法的做用,關於詳細原理有機會再單獨寫寫。

int highestOneBit(int i) : 返回以二進制補碼形式,取左邊最高位 1,後面所有填 0 表示的 int 值
int lowestOneBit(int i)  : 與 highestOneBit() 相反,取其二進制補碼的右邊最低位 1,其他填 0
int numberOfLeadingZeros(int i) : 返回左邊最高位 1 以前的 0 的個數
int numberOfTrailingZeros(int i): 返回右邊最低位 1 以後的 0 的個數
int bitCount(int i) : 二進制補碼中 1 的個數
int rotateRight(int i, int distance) : 將 i 的二進制補碼循環右移 distance(注意與普通右移不一樣的是,右移的數字會移到最左邊)
int rotateLeft(int i, int distance)  : 與 rotateRight 相反
int reverse(int i) : 反轉二進制補碼
int signum(int i)  : 正數返回 1,負數返回 -1,0 返回 0
int reverseBytes(int i) : 以字節爲單位反轉二進制補碼
複製代碼

總結

一個小小的 Integer 類,從頭至尾讀完也花了很多時間,仍是那句名言,Read the fuck sorce code!,源代碼所能給予你的回饋,確定是你意想不到的。

傳送門: 帶註釋 Integer.java 源代碼

文章同步更新於微信公衆號: 秉心說 , 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

相關文章
相關標籤/搜索