Java 基礎:Integer 源碼分析

類定義屬性一、私有屬性二、公共屬性方法構造方法Integer valueOf()方法String 轉成 Integer(int)的方法一、getInteger()方法二、decode()方法總結int 轉成 String 的方法getSize 使用了的體系結構知識:問題1、爲何在 getChars 方法中,將整型數字寫入到字符數組的過程當中爲何按照數字65536分紅了兩部分呢?這個65535是怎麼來的?問題2、在上面兩段代碼的部分二中,在對i進行除十操做的過程當中爲何選擇先乘以 52429 在向右移位 19 位。其中 52429 和 19 是怎麼來的?getChars使用了的體系結構知識總結compareTo 方法實現 Number 的方法equals 和 hashCode 方法javascript

轉載自 Java 源碼學習系列(三)——Integer
學習的過程當中根據本身的理解加以修改。java

基本數據類型 int 的包裝類即爲 Integer,縱觀 Integer、Long、Float、Double 數值類的源碼,以爲 Integer 類的內容更加豐富,包含的小知識點更多,所以決定研究一下該類的源碼,順便作一下知識總結。該類提供了多個方法,能在 int 類型和 String 類型之間互相轉換,還提供了處理 int 類型時很是有用的其餘一些常量和方法。git

類定義

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

從類定義中咱們能夠知道如下幾點:web

一、Integer 類不能被繼承(其餘包裝類也都是 final 修飾的,不可被繼承)數組

二、Integer 類實現了 Comparable 接口,因此能夠用 compareTo 進行比較而且 Integer 對象只能和
Integer 類型的對象進行比較,不能和其餘類型比較(至少調用 compareTo 方法沒法比較)。緩存

三、Integer 繼承了 Number 類,因此該類能夠調用
longValue、floatValue、doubleValue等系列方法返回對應的類型的值。app

屬性

一、私有屬性

Integer 類中定義瞭如下幾個私有屬性:less

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

serialVersionUID 和序列化有關。String 的源碼學習中有介紹,這裏再也不贅述。ide

還有一個私有屬性——value屬性就是Integer對象中真正保存int值的。函數

當咱們使用new Integer(10)建立一個Integer對象的時候,就會用如下形式給value賦值。還有其餘的構造函數在後面會講。

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

這裏咱們討論一下 Interger 對象的可變性。從 value 的定義形式中能夠看出 value 被定義成 final 類型。也就說明,一旦一個 Integer 對象被初始化以後,就沒法再改變 value 的值。那麼這裏就深刻討論一下如下代碼的邏輯:

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 對象呢?

咱們將上面的代碼進行反編譯,反編譯以後的代碼以下:

在這裏插入圖片描述
在這裏插入圖片描述

經過看反編譯以後的代碼咱們發現,編譯器會把 i=5 本質上是調用 Integer.valueOf()方法;這裏先直接給出結論,i=5 操做並無改變 new Integer(10)對象中的 value 值,只是將 i 指向了另一個對象;給 Integer 類型的變量賦值。要麼是直接返回一個已有對象,要麼新建一個對象。這裏的具體實現細節在後面講解 valueOf 方法的時候給出。

二、公共屬性

//值爲 (-(2的31次方)) 的常量,它表示 int 類型可以表示的最小值。
public static final int MIN_VALUE = -2147483648;
//值爲 ((2的31次方)-1) 的常量,它表示 int 類型可以表示的最大值。
public static final int MAX_VALUE = 2147483647;
//表示基本類型 int 的 Class 實例。
public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
static final char[] digits = new char[]{'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'};
static final char[] DigitTens = new char[]{'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'};
static final char[] DigitOnes = new char[]{'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'};
static final int[] sizeTable = new int[]{9999999999999999999999999999999999999999999992147483647};
//用來以二進制補碼形式表示 int 值的比特位數。
public static final int SIZE = 32;
//int的字節數
public static final int BYTES = 4;
複製代碼

方法

構造方法

Integer 提供了兩個構造方法:

public Integer(int var1) {
    this.value = var1;
}

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

從構造方法中咱們能夠知道,初始化一個 Integer 對象的時候只能建立一個十進制的整數,後續會講解 parseInt()方法。

Integer valueOf()方法

valueOf()方法被聲明爲靜態方法,共有三種實現方式。

public static Integer valueOf(String var0, int var1) throws NumberFormatException {
    return parseInt(var0, var1);
}

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

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

首先分析 Integer valueOf(int var0) 方法,在 Java 中基於各類數據類型分析 == 和 equals 的區別一節中有提到過,當傳進 valueOf 的參數在[-128,127]範圍內,則會從常量池中返回已有對象,而不會從新 new 一個對象。以下例所示:

Integer i = 100;//轉換爲Integer i = Integer.valueOf(100)
Integer j = 100;
System.out.println("i,it's memory address:" + System.identityHashCode(i));
System.out.println("j,it's memory address:" + System.identityHashCode(j));
Integer ii = 128;//轉換爲Integer ii = Integer.valueOf(128)
Integer jj = 128;
System.out.println("ii,it's memory address:" + System.identityHashCode(ii));
System.out.println("jj,it's memory address:" + System.identityHashCode(jj));

//執行結果爲:
i,it's memory address:21685669
j,it'
s memory address:21685669
ii,it's memory address:2133927002
jj,it'
s memory address:1836019240
複製代碼

根據輸出結果可知,在[-128,127]範圍內,返回的是同一個對象地址,反之則從新生成一個新的對象。

這裏就須要提一下 Integer 類中的私有靜態類 IntegerCache,該部份內容會在 Integer 類被加載的時候就執行。

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

    private IntegerCache() {
    }

    static {
        int var0 = 127;
        String var1 = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        int var2;
        if (var1 != null) {
            try {
                var2 = Integer.parseInt(var1);
                var2 = Math.max(var2, 127);
                var0 = Math.min(var2, 2147483518);
            } catch (NumberFormatException var4) {
            }
        }

        high = var0;
        cache = new Integer[high - -128 + 1];
        var2 = -128;

        for(int var3 = 0; var3 < cache.length; ++var3) {
            cache[var3] = new Integer(var2++);
        }

        assert high >= 127;

    }
}
複製代碼

當 Integer 被加載時,就新建了-128 到 127 的全部數字並存放在 Integer 數組 cache 中。

再回到 valueOf 代碼,能夠得出結論。當調用 valueOf 方法(包括後面會提到的重載的參數類型包含 String 的 valueOf 方法)時,若是參數的值在[-128,127]之間,則直接從緩存中返回一個已經存在的對象。若是參數的值不在這個範圍內,則 new 一個 Integer 對象返回。

因此,當把一個 int 變量轉成 Integer 的時候(或者新建一個 Integer 的時候),建議使用 valueOf 方法來代替構造函數。或者直接使用 Integer i = 100

當 valueOf 方法中有字符串參數時,實質上是調用 parseInt 方法,這裏分析一下該方法。

//當傳入的字符串含有中英文字符時,會提示報錯。目前測試僅支持數字字符串,不包括小數點,能夠包含正負符號
public static int parseInt(String var0, int var1) throws NumberFormatException {//var1爲10
    if (var0 == null) {
        throw new NumberFormatException("null");
    } else if (var1 < 2) {
        throw new NumberFormatException("radix " + var1 + " less than Character.MIN_RADIX");
    } else if (var1 > 36) {
        throw new NumberFormatException("radix " + var1 + " greater than Character.MAX_RADIX");
    } else {
        int var2 = 0;
        boolean var3 = false;
        int var4 = 0;
        int var5 = var0.length();
        int var6 = -2147483647;
        if (var5 > 0) {
            char var9 = var0.charAt(0);//獲取字符串首字符,肯定最後結果的正負
            if (var9 < '0') {//Character.hashCode('0')等於48,'-'和'+'均小於它
                if (var9 == '-') {//假如var0爲「-1234」時,當首字符爲'-',最後結果也爲負值,不然都爲正值
                    var3 = true;
                    var6 = -2147483648;
                } else if (var9 != '+') {
                    throw NumberFormatException.forInputString(var0);
                }

                if (var5 == 1) {
                    throw NumberFormatException.forInputString(var0);
                }

                ++var4;
            }

            int var8;
            for(int var7 = var6 / var1; var4 < var5; var2 -= var8) {
            //在指定的基數返回字符ch的數值,在此程序中基數爲十進制,若爲中文字符,返回爲-1
                var8 = Character.digit(var0.charAt(var4++), var1);
                if (var8 < 0) {
                    throw NumberFormatException.forInputString(var0);
                }

                if (var2 < var7) {
                    throw NumberFormatException.forInputString(var0);
                }

                var2 *= var1;
                if (var2 < var6 + var8) {
                    throw NumberFormatException.forInputString(var0);
                }
            }

            return var3 ? var2 : -var2;
        } else {
            throw NumberFormatException.forInputString(var0);
        }
    }
}

public static int parseInt(String var0) throws NumberFormatException {
    return parseInt(var0, 10);
}
複製代碼

使用第二個參數指定的基數(若是沒指定,則按照十進制處理),將字符串參數解析爲有符號的整數。除了第一個字符能夠是用來表示負值的 ASCII 減號 ‘-‘ (‘\u002D’)外,字符串中的字符必須都是指定基數的數字(經過 Character.digit(char, int) 是否返回一個負值肯定)。返回獲得的整數值。

若是發生如下任意一種狀況,則拋出一個 NumberFormatException 類型的異常:

字符串爲 null 或一個長度爲零的字符串。

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

假如字符串的長度不小於 1,那麼除了第一個字符(hashCode 小於'0'的字符)能夠是減號 ‘-‘ 或加號'+'外,字符串中存在任意不是由指定基數的數字表示的字符。此外僅有一個字符(hashCode 小於'0')的狀況也會報錯。

字符串表示的值不是 int 類型的值,即存在中文字符。

示例:

public static void parseInt(String var0,int var1){
   System.out.println(Integer.parseInt(var0,var1));
}

parseInt("0"10); //返回 0
parseInt("473"10); //返回 473
parseInt("-0"10);// 返回 0
parseInt("+0"10);// 返回 0
parseInt("12345",16);//74565
parseInt("acbe",16);//44222
parseInt("-FF"16);// 返回 -255
parseInt("1100110"2);// 返回 102
parseInt("2147483647"10);// 返回 2147483647
parseInt("-2147483648"10);// 返回 -2147483648
parseInt("2147483648"10);// 拋出 NumberFormatException
parseInt("77"8);// 返回 63
parseInt("99"8);// 拋出 NumberFormatException
parseInt("Hollis"10);// 拋出 NumberFormatException
parseInt("Hollis"27);// 拋出 NumberFormatException
parseInt("ADMIN"27);// 返回 5586836
複製代碼

根據結果可知,當基數爲 n 時,第一個參數中的字符串分爲兩部分:符號和字符序列,字符序列中每一個字符的大小不得超過 n。好比說當基數爲 16 時,字符串中的字符在[0,9]和[a,f]以及[A,F]取值,不然會拋出異常。

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()方法
public static Integer getInteger(String var0, Integer var1) {
    String var2 = null;

    try {
        var2 = System.getProperty(var0);
    } catch (NullPointerException | IllegalArgumentException var4) {
    }

    if (var2 != null) {
        try {
            return decode(var2);
        } catch (NumberFormatException var5) {
        }
    }

    return var1;
}
複製代碼

肯定具備指定名稱的系統屬性的整數值。 第一個參數被視爲系統屬性的名稱。經過 System.getProperty(java.lang.String) 方法能夠訪問系統屬性。而後,將該屬性的字符串值解釋爲一個整數值,並返回表示該值的 Integer 對象。使用 getProperty 的定義能夠找到可能出現的數字格式的詳細信息。

Properties props = System.getProperties();
Set<Object> set = props.keySet();
for(Object obj:set){
    System.out.println(String.format("%s----:%s",obj.toString(),props.getProperty(obj.toString())));
}
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)
複製代碼
System.out.println(Integer.getInteger("hello"));//null
System.out.println(Integer.getInteger("hello",10));//輸出10
System.out.println(Integer.getInteger("",10));//輸出10
System.out.println(Integer.getInteger(null,10));//輸出10
複製代碼

第二個參數是默認值。若是未具備指定名稱的屬性,或者屬性的數字格式不正確,或者指定名稱爲空或 null,則返回默認值。

二、decode()方法
//將 String 解碼爲 Integer。接受十進制、十六進制和八進制數字
public static Integer decode(String var0) throws NumberFormatException {//var0由三部分組成:符號、基數說明符和字符序列
    byte var1 = 10;//基數默認爲十進制
    int var2 = 0;
    boolean var3 = false;
    if (var0.length() == 0) {
        throw new NumberFormatException("Zero length string");
    } else {
        char var5 = var0.charAt(0);
        if (var5 == '-') {//符號判斷
            var3 = true;
            ++var2;
        } else if (var5 == '+') {
            ++var2;
        }
        //0x,0X,#表示十六進制
        if (!var0.startsWith("0x", var2) && !var0.startsWith("0X", var2)) {
            if (var0.startsWith("#", var2)) {
                ++var2;
                var1 = 16;
            } else if (var0.startsWith("0", var2) && var0.length() > 1 + var2) {//0表示八進制
                ++var2;
                var1 = 8;
            }
        } else {
            var2 += 2;
            var1 = 16;
        }

        if (!var0.startsWith("-", var2) && !var0.startsWith("+", var2)) {
            Integer var4;
            try {
                var4 = valueOf(var0.substring(var2), var1);
                var4 = var3 ? -var4 : var4;
            } catch (NumberFormatException var8) {
                String var7 = var3 ? "-" + var0.substring(var2) : var0.substring(var2);
                var4 = valueOf(var7, var1);
            }

            return var4;
        } else {
            throw new NumberFormatException("Sign character in wrong position");
        }
    }
}
複製代碼

使用例子舉例以下:

Integer DecimalI1 = Integer.decode("+10");
Integer OctI = Integer.decode("-010");
Integer HexI1 = Integer.decode("-0x10");
Integer HexI2 = Integer.decode("#10");
Integer HexI3 = Integer.decode("0X10");
System.out.println(DecimalI1);
System.out.println(OctI);
System.out.println(HexI1);
System.out.println(HexI2);
System.out.println(HexI3);
//10 -8 -16 16 16
複製代碼

decode 方法的具體實現也比較簡單,首先就是判斷 String 類型的參數 var 是否以(+/—)符號開頭。而後再依次判斷是否以」0x」、「#」、「0」開頭,肯定基數說明符(進制數,主要分爲八進制、十進制、十六進制)的值。而後將字符串 var 進行截取,只保留其中純數字部分。在用截取後的純數字和基數調用 valueOf(String s, int radix)方法並返回其值。

至於 valueOf 方法和 parseInt 方法在以前也作過度析。

總結

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

parseInt 方法接收字符串參數,返回的是基本類型int

其餘的方法返回的是 Integer

valueOf(String)方法會調用 parseInt (String) 方法。

若是隻須要返回一個基本類型,而不須要一個對象,能夠直接使用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()方法開始講起,雖然定義很簡單,無須傳入參數,將一個 Integer 類型的數字轉換爲字符串類型,平時常用該方法,調用起來很是簡單,可是閱讀該方法後,就會以爲其實現方式很是優秀。

 public String toString() {
     return toString(this.value);
 }
複製代碼

toString()方法不是靜態方法,須要 Integer 對象來調用,實質上又是調用 Integer 類的靜態方法 toString(int i)。

public static String toString(int var0) {
    if (var0 == -2147483648) {
        return "-2147483648";
    } else {
        int var1 = var0 < 0 ? stringSize(-var0) + 1 : stringSize(var0);
        char[] var2 = new char[var1];
        getChars(var0, var1, var2);
        return new String(var2, true);
    }
}
複製代碼

上述代碼挨個進行分析,首先是:

if (var0 == -2147483648) {
   return "-2147483648";

複製代碼

這裏對 int 值作校驗,若是等於 Int 能表示的最小值,則直接返回最小值的字符串形式。那麼爲何-2147483648 要特殊處理呢?接下來會繼續分析。

int var1 = var0 < 0 ? stringSize(-var0) + 1 : stringSize(var0);
char[] var2 = new char[var1];
複製代碼

這段代碼主要是爲了計算出 int 值的位數,並建立一個 char 數組。其中計算位數調用的是 stringSize()方法,該方法實現以下:

static final int[] sizeTable = new int[]{9999999999999999999999999999999999999999999992147483647};

static int stringSize(int var0) {
    int var1;
    for(var1 = 0; var0 > sizeTable[var1]; ++var1) {
    }

    return var1 + 1;
}
複製代碼

該方法要求傳入一個正整數,若是傳入的數字 var0 的值是 10000,那麼由於它大於 9,99,999,9999,小於 99999。因此它會返回 99999 在整型數組 sizeTable 中的下標 4,而後加1 返回 5。咱們看 10000 這個數字的位數也確實是 5。因此,就實現了返回一個正整數的位數。

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

因爲 stringSize 方法要求傳入一個正整數,若是是負數,須要將負數轉成正數傳入。代碼片斷一中,將-2147483648的值直接返回的緣由就是 int 最大隻能表示 2147483647,沒法將 stringSize(-var0)中的i賦值成 -2147483648。

getSize 使用了的體系結構知識:
1.局部性原理之空間局部性:sizeTable爲數組,存儲在相鄰的位置,cpu一次加載一個塊數組數據到cache中(多個數組數據),此後訪問sizeTable 不須要訪問內存。

2.基於範圍的查找,是很實用的設計技術
複製代碼

接着分析代碼以下:

getChars(var0, var1, var2);
複製代碼

那麼接下來就深刻理解一下 getChars 方法。這部分我把關於這段代碼的分析直接寫到註釋中,便於結合代碼理解。

static void getChars(int var0, int var1, char[] var2) {
    int var5 = var1;
    byte var6 = 0;
    if (var0 < 0) {
        var6 = 45;//'-'的hashCode爲45,後續(char)45能夠轉換爲'-'
        var0 = -var0;
    }

    int var3;
    int var4;
    //當var0值高於兩字節,每次循環事後,會將var0中的最後後兩位保存到字符數組var2中的最後兩位中
    //當var0爲12345678時,var2數組長度var1爲8,第一次循環結束以後,var2[7] = 8,var2[6]=7。第二次循環結束以後,var2[5] = 6,var2[4] = 5。
    while(var0 >= 65536) {
        var3 = var0 / 100;
        var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));
        var0 = var3;
        --var5;
        //取DigitOnes[r]的目的其實取數字r%10的結果
        var2[var5] = DigitOnes[var4];
        --var5;
        //取DigitTens[r]的目的實際上是取數字r/10的結果
        var2[var5] = DigitTens[var4];
    }

    //循環將低兩字節數字存入字符數組中空餘位置
    do {
        //這裏其實就是除以10。取數52429和19的緣由在後文分析
        var3 = var0 * 52429 >>> (19);
        var4 = var0 - ((var3 << 3) + (var3 << 1));
        --var5;
        //將數字i的最後一位存入字符數組,
        //仍是12345678那個例子,這個for循環第一次結束後,buf[3]=4。
        var2[var5] = digits[var4];
        var0 = var3;
    } while(var3 != 0);

    if (var6 != 0) {//爲負數的時候,數組第一個字符加上'-'
        --var5;
        var2[var5] = (char)var6;
    }

}
複製代碼

其中用到的 DigitTens、DigitOnes 和 digits 數組在 Integer 類中有聲明,好比取 DigitTens[78],返回的是數字 7,DigitOnes[78]返回數字 8。

接下來分析兩個問題:

問題1、爲何在 getChars 方法中,將整型數字寫入到字符數組的過程當中爲何按照數字65536分紅了兩部分呢?這個65535是怎麼來的?

部分一

 while(var0 >= num1) {
     var3 = var0 / 100;
     var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));
     var0 = var3;
     --var5;
     var2[var5] = DigitOnes[var4];
     --var5;
     var2[var5] = DigitTens[var4];
 }
複製代碼

部分二

    do {
        var3 = var0 * num2>>> (num3);
        var4 = var0 - ((var3 << 3) + (var3 << 1));
        --var5;
        //將數字i的最後一位存入字符數組,
        //仍是12345678那個例子,這個for循環第一次結束後,buf[3]=4。
        var2[var5] = digits[var4];
        var0 = var3;
    } while(var3 != 0);
複製代碼

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

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

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

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

乘法的效率比除法的效率要高
複製代碼

先理解如下代碼:

var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));表示的實際上是var4=var0 - (var3 * 100);,var0-var3 *2^6 - var3 *2^5 - var3 *2^2 = var0-64*var3 -32*var3 -4*var3 = var0-100*var3 。

var3 = var0 * num2>>> (num3);中,>>>表示無符號向右移位。表明的意義就是除以2^num3。 因此 var3 = (var0 * 52429) >>> (16+3); 能夠理解爲:var3 = (var0 * 52429) / 524288;那麼就至關於 var3= var0 * 0.1 也就是 var3=var0/10,這樣經過乘法和向右覺得的組合的形式代替了除法,能提升效率。

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

既然咱們要使用 var3 = var0 * num2>>> (num3); 的形式使用乘法和移位代替除法,那麼 num2 和 num3 就要有這樣的關係:num2= (2^num3 /10 +1)

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

那麼 52429 這個數是怎麼來的呢?來看如下數據:

2^10=1024103/1024=0.1005859375
2^11=2048205/2048=0.10009765625
2^12=4096410/4096=0.10009765625
2^13=8192820/8192=0.10009765625
2^14=163841639/16384=0.10003662109375
2^15=327683277/32768=0.100006103515625
2^16=655366554/65536=0.100006103515625
2^17=13107213108/131072=0.100006103515625
2^18=26214426215/262144=0.10000228881835938
2^19=52428852429/524288=0.10000038146972656
2^20=1048576104858/1048576=0.1000003815
2^21=2097152209716/2097152 = 0.1000003815
2^224194304419431/41943040.1000001431
複製代碼

超過 22 的數字就不列舉了,由於若是 num3 越大,就會要求 var0 比較小,由於必須保證(var0 * num2) >>> (num3)的過程不會由於溢出而致使數據不許確。那麼是怎麼敲定 num1=65536,num2= 524288, num3=19 的呢? 這三個數字之間是有這樣一個操做的:(num1* num2)>>> num3

由於要保證該操做不能由於溢出致使數據不許確,因此 num1 和 num2 就相互約束。兩個數的乘積是有必定範圍的,不成超過這個範圍,因此,num1 增大,num2 就要隨之減少。

我以爲有如下幾個緣由:

1.65536正好是2^16,一個整數佔4個字節。65536正好佔了2個字節,選定這樣一個數字有利於CPU訪問數據。
2.52429/524288=0.10000038146972656精度足夠高。

3.下一個精度較高的num2和num3的組合是419431和22,此時 num1*num2會內存溢出。

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

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

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

getChars使用了的體系結構知識

1.乘法比除法高效:var3 = var0 * 52429 >>> (16+3); => 約等於 var00.1, var052429是整數乘法器,結合位移避免除法。

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

3.位移比乘法高效:var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));等價於 var4 = var0 – (var3 * 100);

4.局部性原理之空間局部性

(1).var2[var5] =DigitOnes[var4 ];buf[var5]=DigitTens[var4 ];經過查找數組,實現快速訪問,避免除法計算

(2).var2[var5] = digits [var4];

最後一行代碼:

new String(var2, true);
複製代碼

這裏用到了一個 String 中提供的保護類型構造函數,關於此函數請查看String源碼分析,該函數比使用其餘的構造函數有更好的性能。

總結

因此,經過調用 Integer 類的靜態方法可以將 int 值轉成 String 類型。除了上面提到的一系列方法外,通常在要使用 String 的時候,不少人願意使用以下形式:

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

反編譯一下,結果以下:

  public static void main(java.lang.String[]);
    Code:
       0new           #2                  // class java/lang/Integer
       3: dup
       4: sipush        199
       7: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
      10: astore_1
      11: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14new           #5                  // class java/lang/StringBuilder
      17: dup
      18: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      21: aload_1
      22: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      25: ldc           #8                  // String
      27: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      33: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36return
}
複製代碼

能夠看出其實是經過 StringBuilder 對象進行拼接的,使用 JMH 進行了測試,結果證實調用 Integer 本身的方法效率更高。

compareTo 方法

在介紹 Interger 的類定義的時候介紹過,Integer 類實現了Comparable接口,因此 Integer 對象能夠和另一個 Integer 對象進行比較。

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

public static int compare(int var0, int var1) {
    return var0 < var1 ? -1 : (var0 == var1 ? 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;
}
複製代碼

equals 和 hashCode 方法

public static int hashCode(int var0) {
    return var0;
}

public boolean equals(Object var1) {
    if (var1 instanceof Integer) {
        return this.value == (Integer)var1;
    } else {
        return false;
    }
}
複製代碼

Integer 類也重寫了 equals 和 hashCode 方法,實現比較簡單。

相關文章
相關標籤/搜索