文中相關原始碼:
開發的越久,越能體會到基礎知識的重要性。抽空捋一下 JDK 原始碼,權當查漏補缺。讀完之後,你會發現 JDK 原始碼真的會給你很多驚喜。
Integer
是基本型別 int
的包裝類,它提供了一些處理 int
數值的方法,String
和 int
相互轉換的方法。另外,它還提供了一些位運算,這些位運算來自於 Henry S. Warren Jr.
的 《Hacker's Delight》。
類宣告
首先看一下 Integer
的類宣告:
public final class Integer extends Number implements Comparable<Integer>{}
複製程式碼
Inetger
是不可變類,無法被繼承。關於 不可變類 的詳細介紹,可以閱讀我之前的一篇文章 《String 為什麼不可變?》。
Integer
繼承了抽象類 Number
,並實現了它的下列方法: byteValue()
shortValue()
intValue()
longValue()
floatValue()
doubleValue()
,將 int
轉換為其他基本型別的值,實現方法都是強轉。
Integer
還實現了 Comparable
介面,因此也具備了比較物件大小的能力,其 compareTo()
方法具體實現如下:
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
值。0x80000000
和 0x7fffffff
分別是 int 最小值和最大值的十六進位制表示,這裡要注意十六進位制 int 值在記憶體中的表示方法,有興趣的同學可以瞭解一下,這裡先佔個坑,有時間單獨寫一篇文章。
我們都知道 int
是 4 位元組,32 位元,和 C/C++ 不同的時,Java 中整型的取值範圍和執行 Java 程式碼的機器是無關的。無論是 16 位系統,32 位系統,還是 64 位系統,int
永遠都是 4位元組。這也體現了 Java 的 “一次編寫,到處執行”。
建構函式
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
欄位。
String
轉 int
,舉幾個例子還是很容易理解的:
"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()
系列,還有幾個 String
轉 int
的方法也一併分析一下。
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
值,可接受十進位制、十六進位制、八進位制形式的字串。其中,以 0x
、0X
、#
開頭的字串表示十六進位制,以 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.LOW
和 IntegerCache.HIGH
之間的數值,直接返回快取中已經建立好的物件,其餘值每次都建立新的物件。IntegerCache.LOW
為 -128
,IntegerCache.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() {}
}
複製程式碼
String
轉 int
的方法差不多就介紹完了,下面分析 int
轉 String
的方法。
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
值轉化為十進位制形式的字串。這段原始碼真的很精髓,開發者為了提升執行效率無所不用其極,讓人心生敬佩。下面詳細分析一下這個方法:
- 對於
Integer.MIN_VALUE
,直接返回-2147483648
- 通過
stringSize()
方法獲取需要的字串長度size
- 新建字元陣列
buf
,用來儲存字串 - 通過
getChars()
方法填充字元陣列buf
- 通過
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/10
和 r%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
陣列儲存了 0
到 99
對 10
取餘的運算結果。
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
陣列儲存了 0
到 99
除以 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()
方法,這裡就不再多說了。
String
和 int
相互轉換的方法就說到這裡了,大部分方法應該都提到了。最後簡單說說一些位運算。
位運算
類註釋中提到了一本書,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 題解,歡迎關注!