java基礎:Integer — 原始碼分析

Hiway發表於2018-12-16

其他更多java基礎文章: java基礎學習(目錄)


轉載自 Java 原始碼學習系列(三)——Integer
學習的過程中發現這位大神的文章已經非常全面了,如果之後學習還有補充的,我會寫一個原始碼分析(二) 有什麼疑問可以在評論裡問

Integer 類在物件中包裝了一個基本型別 int 的值。Integer 型別的物件包含一個 int 型別的欄位。
此外,該類提供了多個方法,能在 int 型別和 String 型別之間互相轉換,還提供了處理 int 型別時非常有用的其他一些常量和方法。

類定義

public final class Integer extends Number implements Comparable<Integer>

從類定義中我們可以知道以下幾點:

1、Integer類不能被繼承
2、Integer類實現了Comparable介面,所以可以用compareTo進行比較並且Integer物件只能和Integer型別的物件進行比較,不能和其他型別比較(至少呼叫compareTo方法無法比較)。
3、Integer繼承了Number類,所以該類可以呼叫longValue、floatValue、doubleValue等系列方法返回對應的型別的值。

屬性

一、私有屬性

Integer類中定義了以下幾個私有屬性:

private final int value;
private static final long serialVersionUID = 1360826667806852920L;
複製程式碼

serialVersionUID和序列化有關。

因為Integer實現了Serializable介面,所以支援序列化和反序列化支援。Java的序列化機制是通過在執行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常(InvalidCastException)。

還有一個私有屬性——value屬性就是Integer物件中真正儲存int值的。

當我們使用new Integer(10)建立一個Integer物件的時候,就會用以下形式給value賦值。還有其他的建構函式在後面會講。

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

這裡我們討論一下Interger物件的可變性。從value的定義形式中可以看出value被定義成final型別。也就說明,一旦一個Integer物件被初始化之後,就無法再改變value的值。那麼這裡就深入討論一下以下程式碼的邏輯:

/**
 * Created by hollis on 16/1/22.
 */
public class IntegerTest {
    public static void main(String[] args) {
        Integer i = new Integer(10);
        i = 5;
    }
}
複製程式碼

在以上程式碼中,首先呼叫建構函式new一個Integer物件,給私有屬性value賦值,這時value=10,接下來使用i=5的形式試圖改變i的值。有一點開發經驗的同學都知道,這個時候如果使用變數i,那麼它的值一定是5,那麼i=5這個賦值操作到底做了什麼呢?到底是如何改變i的值的呢?是改變了原有物件i中value的值還是重新建立了一個新的Integer物件呢?

我們將上面的程式碼進行反編譯,反編譯之後的程式碼如下:

public class IntegerTest
{

    public IntegerTest()
    {
    }

    public static void main(String args[])
    {
        Integer i = new Integer(10);
        i = Integer.valueOf(5);
    }
}
複製程式碼

通過看反編譯之後的程式碼我們發現,編譯器會把i=5轉成i = Integer.valueOf(5);這裡先直接給出結論,i=5操作並沒有改變使用Integer i = new Integer(10);建立出來的i中的value屬性的值。要麼是直接返回一個已有物件,要麼新建一個物件。這裡的具體實現細節在後面講解valueOf方法的時候給出。

二、公共屬性

//值為 (-(2的31次方)) 的常量,它表示 int 型別能夠表示的最小值。
public static final int   MIN_VALUE = 0x80000000;
//值為 ((2的31次方)-1) 的常量,它表示 int 型別能夠表示的最大值。
public static final int   MAX_VALUE = 0x7fffffff;   
//表示基本型別 int 的 Class 例項。
public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
//用來以二進位制補碼形式表示 int 值的位元位數。
public static final int SIZE = 32;
//用來以二進位制補碼形式表示 int 值的位元組數。1.8以後才有
public static final int BYTES = SIZE / Byte.SIZE;
複製程式碼

以上屬性可直接使用,因為他們已經定義成publis static fianl能用的時候儘量使用他們,這樣不僅能使程式碼有很好的可讀性,也能提高效能節省資源。

方法

構造方法

Integer提供了兩個構造方法:

//構造一個新分配的 Integer 物件,它表示指定的 int 值。
public Integer(int value) {
    this.value = value;
}

//構造一個新分配的 Integer 物件,它表示 String 引數所指示的 int 值。
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}
複製程式碼

從構造方法中我們可以知道,初始化一個Integer物件的時候只能建立一個十進位制的整數。

Integer valueOf(int i)方法

前面說到Integer中私有屬性value的時候提到

Integer i = new Integer(10);
i = 5;
複製程式碼

其中i=5操作時,編譯器會轉成i = Integer.valueOf(5);執行。那麼這裡就解釋一下valueOf(int i)方法是如何給變數賦值的。

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

以上是valueOf方法的實現細節。通常情況下,IntegerCache.low=-128,IntegerCache.high=127(除非顯示宣告java.lang.Integer.IntegerCache.high的值),Integer中有一段動態程式碼塊,該部分內容會在Integer類被載入的時候就執行。

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

也就是說,當Integer被載入時,就新建了-128到127的所有數字並存放在Integer陣列cache中。

再回到valueOf程式碼,可以得出結論。當呼叫valueOf方法(包括後面會提到的過載的引數型別包含String的valueOf方法)時,如果引數的值在-127到128之間,則直接從快取中返回一個已經存在的物件。如果引數的值不在這個範圍內,則new一個Integer物件返回。

所以,當把一個int變數轉成Integer的時候(或者新建一個Integer的時候),建議使用valueOf方法來代替建構函式。或者直接使用Integer i = 100;編譯器會轉成Integer s = Integer.valueOf(100);

String轉成Integer(int)的方法

Integer getInteger(String nm)
Integer getInteger(String nm, int val)
Integer getInteger(String nm, Integer val)
Integer decode(String nm)
Integer valueOf(String s)
Integer valueOf(String s, int radix)
int parseUnsignedInt(String s)
int parseUnsignedInt(String s, int radix)
int parseInt(String s)
int parseInt(String s, int radix)
複製程式碼

以上所有方法都能實現將String型別的值轉成Integer(int)型別(如果 String 不包含可解析整數將丟擲NumberFormatException)

可以說,所有將String轉成Integer的方法都是基於parseInt方法實現的。簡單看一下以上部分方法的呼叫棧。

getInteger(String nm) ---> getInteger(nm, null);--->Integer.decode()--->Integer.valueOf()--->parseInt()
複製程式碼

getInteger

確定具有指定名稱的系統屬性的整數值。 第一個引數被視為系統屬性的名稱。通過 System.getProperty(java.lang.String) 方法可以訪問系統屬性。然後,將該屬性的字串值解釋為一個整數值,並返回表示該值的 Integer 物件。使用 getProperty 的定義可以找到可能出現的數字格式的詳細資訊。其中引數nm應該在System的props中可以找到。這個方法在日常編碼中很好是用到。在程式碼中可以用以下形式使用該方法:

Properties props = System.getProperties();
props.put("hollis.integer.test.key","10000");
Integer i = Integer.getInteger("hollis.integer.test.key");
System.out.println(i);
//輸出 10000
複製程式碼

另外兩個方法

getInteger(String nm,int val)
getInteger(String nm, Integer val)
複製程式碼

第二個引數是預設值。如果未具有指定名稱的屬性,或者屬性的數字格式不正確,或者指定名稱為空或 null,則返回預設值。

getInteger的具體實現細節如下:

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

先按照nm作為key從系統配置中取出值,然後呼叫Integer.decode方法將其轉換成整數並返回。

decode

public static Integer decode(String nm) throws NumberFormatException
複製程式碼

該方法的作用是將 String 解碼為 Integer。接受十進位制、十六進位制和八進位制數字。

根據要解碼的 String(mn)的形式轉成不同進位制的數字。 mn由三部分組成:符號、基數說明符和字元序列。 —0X123-是符號位,0X是基數說明符(0表示八進位制,0x,0X,#表示十六進位制,什麼都不寫則表示十進位制),123是數字字元序列。

使用例子舉例如下:

Integer DecimalI = Integer.decode("+10");
Integer OctI = Integer.decode("-010");
Integer HexI = Integer.decode("-0x10");
Integer HexI1 = Integer.decode("#10");
System.out.println(DecimalI);
System.out.println(OctI);
System.out.println(HexI);
System.out.println(HexI1);
//10 -8 -16 16
複製程式碼

decode方法的具體實現也比較簡單,首先就是判斷String型別的引數mn是否以(+/—)符號開頭。然後再依次判斷是否以”0x”、“#”、“0”開頭,確定基數說明符的值。然後將字串mn進行擷取,只保留其中純數字部分。在用擷取後的純數字和基數呼叫valueOf(String s, int radix)方法並返回其值。

valueOf

public static Integer valueOf(String s) throws NumberFormatException 
public static int parseInt(String s, int radix) throws NumberFormatException
複製程式碼

返回一個 Integer 物件。如果指定第二個引數radix,將第一個引數解釋為用第二個引數指定的基數表示的有符號整數。如果沒指定則按照十進位制進行處理。

該方法實現非常簡單:

public static Integer valueOf(String s) throws NumberFormatException {
     return Integer.valueOf(parseInt(s, 10));
}

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

主要用到了兩個方法,parseInt(String s, int radix)valueOf(int i)方法。前面已經講過valueOf方法會檢查引數內容是否在-127到128之間,如果是則直接返回。否則才會新建一個物件。

parseInt

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

public static int parseInt(String s, int radix) throws NumberFormatException
複製程式碼

使用第二個引數指定的基數(如果沒指定,則按照十進位制處理),將字串引數解析為有符號的整數。除了第一個字元可以是用來表示負值的 ASCII 減號 ‘-‘ (‘\u002D’)外,字串中的字元必須都是指定基數的數字(通過 Character.digit(char, int) 是否返回一個負值確定)。返回得到的整數值。

如果發生以下任意一種情況,則丟擲一個 NumberFormatException 型別的異常:

第一個引數為 null 或一個長度為零的字串。

基數小於 Character.MIN_RADIX 或者大於 Character.MAX_RADIX。

假如字串的長度超過 1,那麼除了第一個字元可以是減號 ‘-‘ (‘u002D’) 外,字串中存在任意不是由指定基數的數字表示的字元.

字串表示的值不是 int 型別的值。

示例:

parseInt("0", 10) 返回 0
parseInt("473", 10) 返回 473
parseInt("-0", 10) 返回 0
parseInt("-FF", 16) 返回 -255
parseInt("1100110", 2) 返回 102
parseInt("2147483647", 10) 返回 2147483647
parseInt("-2147483648", 10) 返回 -2147483648
parseInt("2147483648", 10) 丟擲 NumberFormatException
parseInt("99", 8) 丟擲 NumberFormatException
parseInt("Hollis", 10) 丟擲 NumberFormatException
parseInt("Hollis", 27) 丟擲 NumberFormatException
parseInt("ADMIN", 27) 返回 5586836 
複製程式碼

該方法的具體實現方式也比較簡單,主要邏輯程式碼(省略部分引數校驗)如下:

while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
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;
}
複製程式碼

主要思想其實也很好理解。

“12345”按照十進位制轉成12345的方法其實就是以下方式: ((1*10)+2)*10)+3)*10+4)*10+5 具體的如何依次取出“12345”中的每一個字元並將起轉成不同進位制int型別則是Character.digit方法實現的,這裡就不深入講解了。

總結

上面列舉了很多能夠將String轉成Integer的方法。那麼他們之間有哪些區別,又該如何選擇呢?

parseInt方法返回的是基本型別int

其他的方法返回的是Integer

valueOf(String)方法會呼叫valueOf(int)方法。

如果只需要返回一個基本型別,而不需要一個物件,可以直接使用Integert.parseInt("123");

如果需要一個物件,那麼建議使用valueOf(),因為該方法可以藉助快取帶來的好處。

如果和進位制有關,那麼就是用decode方法。

如果是從系統配置中取值,那麼就是用getInteger

int轉成String的方法

String  toString()
static String   toString(int i)
static String   toString(int i, int radix)
static String   toBinaryString(int i)
static String   toHexString(int i)
static String   toOctalString(int i)
static String   toUnsignedString(int i)
static String   toUnsignedString(int i, int radix)
複製程式碼

直接看toString方法,toString方法的定義比較簡單,就是把一個int型別的數字轉換成字串型別,但是這個方法的實現呼叫了一系列方法,通過閱讀這個方法,你就會對sun公司的程式設計師產生油然的敬佩。

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);
}
複製程式碼

我們把toString方法分解為以下幾個片段:

片段一:

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);
複製程式碼

片段一

if (i == Integer.MIN_VALUE)
        return "-2147483648";
複製程式碼

這裡先對i的值做檢驗,如果等於Int能表示的最小值,則直接返回最小值的字串形式。那麼為什麼-2147483648要特殊處理呢?請看程式碼片段二的分析。

片段二

int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
複製程式碼

這段程式碼的主要目的是體取出整數i的位數,並建立一個字元陣列。 其中提取I的位數使用stringSize方法,這個方法實現如下:

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

該方法要求傳入一個正整數,如果傳入的數字x的值是10000,那麼因為他大於9,99,999,9999,小於99999.所以他會返回99999在整型陣列sizeTable中的下標”4″+1 = 5。我們看10000這個數字的位數也確實是5。所以,就實現了返回一個正整數的位數。

設定size時,當i<0的時候返回的size陣列在stringSize方法的基礎上+1的目的是這一位用來儲存負號。

由於stringSize方法要求傳入一個正整數,所以程式碼片段二在呼叫該方法時需要將負數轉成正數傳入。程式碼片段一中,將-2147483648的值直接返回的原因就是整數最大隻能表示2147483647,無法將stringSize(-i)中的i賦值成-2147483648。

getSize使用了的體系結構知識:

1.區域性性原理之空間區域性性:sizeTable為陣列,儲存在相鄰的位置,cpu一次載入一個塊資料資料到cache中(多個陣列資料),此後訪問sizeTable 不需要訪問記憶體。

2.基於範圍的查詢,是很實用的設計技術

片段三

getChars(i, size, buf);
複製程式碼

那麼接下來就深入理解一下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;
    }

     // 每次迴圈過後,都會將i中的走後兩位儲存到字元陣列buf中的最後兩位中,讀者可以將數字i設定為12345678測試一下, 
     //第一次迴圈結束之後,buf[7] = 8,buf[6]=7。第二次迴圈結束之後,buf[5] = 6,buf[4] = 5。
    while (i >= 65536) {
        q = i / 100;
    // really: r = i - (q * 100);
        r = i - ((q << 6) + (q << 5) + (q << 2));
        i = q;
        //取DigitOnes[r]的目的其實取數字r%10的結果
        buf [--charPos] = DigitOnes[r];
        //取DigitTens[r]的目的其實是取數字r/10的結果
        buf [--charPos] = DigitTens[r];
    }

    // Fall thru to fast mode for smaller numbers
    // assert(i <= 65536, i);
    //迴圈將其他數字存入字元陣列中空餘位置
    for (;;) {
          //這裡其實就是除以10。取數52429和16+3的原因在後文分析。
        q = (i * 52429) >>> (16+3);
        // r = i-(q*10) ...
        r = i - ((q << 3) + (q << 1));   
        //將數字i的最後一位存入字元陣列,
        //還是12345678那個例子,這個for迴圈第一次結束後,buf[3]=4。
        buf [--charPos] = digits [r];
        i = q;
        //for迴圈結束後,buf內容為“12345678”;
        if (i == 0) break;
    }
    if (sign != 0) {
        buf [--charPos] = sign;
    }
}

//其中用到的幾個陣列

//100以內的數字除以10的結果(取整),
//比如取DigitTens[78],返回的是數字7
//只要是70-79的數字,返回的都是7,依次類推,所以總結出規律,其實就是返回的對應數字除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',
    } ;

 //100以內的數字對10取模的結果,
//比如取DigitTens[78],返回的8
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',
    } ;
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'
};
複製程式碼

接下來分析兩個問題:

問題一、為什麼在getChars方法中,將整型數字寫入到字元陣列的過程中為什麼按照數字65536分成了兩部分呢?這個65535是怎麼來的?

部分一

while (i >= num1) {
        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 * num2) >>> (num3);
        r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
        buf [--charPos] = digits [r];
        i = q;
        if (i == 0) break;
    }
複製程式碼

使用num1,num2,num3三個變數代替原始碼中的數字,便於後面分析使用。

問題二、在上面兩段程式碼的部分二中,在對i進行除十操作的過程中為什麼選擇先乘以52429在向右移位19位。其中52429和19是怎麼來的?

解答

回答上面兩個問題之前,首先要明確兩點:

移位的效率比直接乘除的效率要高

乘法的效率比除法的效率要高

先理解以下程式碼:

r = i - ((q << 6) + (q << 5) + (q << 2));表示的其實是r = i - (q * 100);i-q*2^6 - q*2^5 - q*2^2 = i-64q-32q-4q = i-100q

q = (i * num2) >>> (num3);中,>>>表示無符號向右移位。代表的意義就是除以2^num3。 所以q = (i * 52429) >>> (16+3); 可以理解為:q = (i * 52429) / 524288;,那麼就相當於 q= i * 0.1也就是q=i/10,這樣通過乘法和向右以為的組合的形式代替了除法,能提高效率。

再來回答上面兩個問題中,部分一和部分二中最大的區別就是部分一程式碼使用了除法,第二部分只使用了乘法和移位。因為乘法和移位的效率都要比除法高,所以第二部分單獨使用了乘法加移位的方式來提高效率。那麼為什麼不都使用乘法加移位的形式呢?為什麼大於num1(65536)的數字要使用除法呢?原因是int型變數最大不能超過(2^31-1)。如果使用一個太大的數字進行乘法加移位運算很容易導致溢位。那麼為什麼是65536這個數字呢?第二階段用到的乘法的數字和移位的位數又是怎麼來的呢?

我們再回答第二個問題。

既然我們要使用q = (i * num2) >>> (num3);的形式使用乘法和移位代替除法,那麼n和m就要有這樣的關係:

num2= (2^num3 /10 +1)

只有這樣才能保證(i * num2) >>> (num3)結果接近於0.1。

那麼52429這個數是怎麼來的呢?來看以下資料:

2^10=1024, 103/1024=0.1005859375
2^11=2048, 205/2048=0.10009765625
2^12=4096, 410/4096=0.10009765625
2^13=8192, 820/8192=0.10009765625
2^14=16384, 1639/16384=0.10003662109375
2^15=32768, 3277/32768=0.100006103515625
2^16=65536, 6554/65536=0.100006103515625
2^17=131072, 13108/131072=0.100006103515625
2^18=262144, 26215/262144=0.10000228881835938
2^19=524288, 52429/524288=0.10000038146972656
2^20=1048576, 104858/1048576=0.1000003815
2^21=2097152, 209716/2097152 = 0.1000003815
2^22= 4194304, 419431/4194304= 0.1000001431
複製程式碼

超過22的數字我就不列舉了,因為如果num3越大,就會要求i比較小,因為必須保證(i * num2) >>> (num3)的過程不會因為溢位而導致資料不準確。那麼是怎麼敲定num1=65536,num2= 524288, num3=19的呢? 這三個數字之間是有這樣一個操作的:

(num1* num2)>>> num3
複製程式碼

因為要保證該操作不能因為溢位導致資料不準確,所以num1和num2就相互約束。兩個數的乘積是有一定範圍的,不成超過這個範圍,所以,num1增大,num2就要隨之減小。

我覺得有以下幾個原因:

1.52429/524288=0.10000038146972656精度足夠高。

2.下一個精度較高的num2和num3的組合是419431和22。2^31/2^22 = 2^9 = 512。512這個數字實在是太小了。65536正好是2^16,一個整數佔4個位元組。65536正好佔了2個位元組,選定這樣一個數字有利於CPU訪問資料。

不知道有沒有人發現,其實65536* 52429是超過了int的最大值的,一旦超過就要溢位,那麼為什麼還能保證(num1* num2)>>> num3能得到正確的結果呢?

這和>>>有關,因為>>>表示無符號右移,他會在忽略符號位,空位都以0補齊。

一個有符號的整數能表示的範圍是-2147483648至2147483647,但是無符號的整數能表示的範圍就是0-4,294,967,296(2^32 ),所以,只要保證num2*num3的值不超過2^32 次方就可以了。65536是2^16 ,52429正好小於2^16 ,所以,他們的乘積在無符號向右移位就能保證數字的準確性。

getChars使用了的體系結構知識:

1.乘法比除法高效:q = ( i * 52429) >>> (16+3); => 約等於q0.1,但i52429是整數乘法器,結合位移避免除法。

2.重複利用計算結果:在獲取r(i%100)時,充分利用了除法的結果,結合位移避免重複計算。

3.位移比乘法高效:r = i – (( q << 6) + ( q << 5) + ( q << 2)); = >等價於r = i – (q * 100);

4.區域性性原理之空間區域性性

(1).buf[–charPos] =DigitOnes[r];buf[–charPos] =DigitTens[r];通過查詢陣列,實現快速訪問,避免除法計算

(2).buf [–charPos ] = digits [ r];

片段四

return new String(buf, true);
複製程式碼

這裡用到了一個String中提供的保護型別建構函式,關於此函式請檢視java基礎:String — 原始碼分析(一) ,該函式比使用其他的建構函式有更好的效能。

總結

所以,一個Integer物件有很多方法能夠將值轉成String型別。除了上面提到的一系列方法外,一般在要使用String的時候,很多人願意使用如下形式:

Integer s = new Integer(199);
System.out.println(s + "");
複製程式碼

老規矩,反編譯看看怎麼實現的:

Integer s = new Integer(199);
System.out.println((new StringBuilder()).append(s).append("").toString());
複製程式碼

筆者使用JMH進行了測試,結果證明方法效率更高。

compareTo方法

在看是介紹Interger的類定義的時候介紹過,Integer類實現了Comparable<Integer>介面,所以Integer物件可以和另外一個Integer物件進行比較。

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);
}
複製程式碼

程式碼實現比較簡單,就是拿出其中的int型別的value進行比較。

實現Number的方法

int intValue();
long longValue();
float floatValue();
double doubleValue();
byte byteValue();
short shortValue();
複製程式碼

實現如下:

public long longValue() {
    return (long)value;
}

public float floatValue() {
    return (float)value;
}

public double doubleValue() {
    return (double)value;
}
複製程式碼

相關文章