常用資料類型大家都會用,JDK也為了保持一切皆對象的原則,将8大常用資料類型都封裝成了對應的對象,本次先來分析JDK怎麼封裝的Integer。
首先看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;
}
}
今天就先到這裡吧!!!有時間繼續。。。