1. 程式人生 > >Java進階--從原始碼理解Integer類的toString方法

Java進階--從原始碼理解Integer類的toString方法

Java中的jdk是深入學習Java程式設計的寶庫,其中各種方法的實現不僅可以深入瞭解 設計模式的應用,還可以看到jdk編寫者如何優化程式碼(這種優化已經到達了一種極端的程度)對我們編寫自己的程式碼是有十分重要的作用的。read the fucking source code 是成為一名優秀程式猿必須的經歷。本部落格通過分析Integer類的toString方法來看看jdk編寫者如何編寫他們的程式碼的。

Integer類的toString的基本用法

public class IntegerDemo {
    public static void main(String[] args) {

        Integer OUT_MAX_VALUE=new Integer(Integer.MAX
_VALUE+1); Integer MAX_VALUE=new Integer(Integer.MAX_VALUE); Integer MIN_VALUE=new Integer(Integer.MIN_VALUE); Integer NOR_VALUE=new Integer(-128); Integer OUT_MIN_VALUE=new Integer(Integer.MIN_VALUE-1); System.out.println("max_val :"+MAX_VALUE.toString
()); System.out.println("max_val :"+MAX_VALUE); System.out.println("-------------------------"); System.out.println("out_max :"+OUT_MAX_VALUE.toString()); System.out.println("out_min :"+OUT_MIN_VALUE.toString()); System.out.println("-------------------------");
System.out.println("min_val :"+MIN_VALUE.toString()); System.out.println("min_val :"+MIN_VALUE); System.out.println("-------------------------"); System.out.println("nor_val :"+NOR_VALUE.toString()); System.out.println("nor_val :"+NOR_VALUE); } } //輸出 /* max_val :2147483647 max_val :2147483647 ------------------------- out_max :-2147483648 out_min :2147483647 ------------------------- min_val :-2147483648 min_val :-2147483648 ------------------------- nor_val :-128 nor_val :-128 */

由上面可知直接輸出Integer的值和呼叫Integer類的toString方法是一樣的,其實直接列印一個物件的時候就是呼叫了該物件的toString方法。呼叫toString方法的時候其實輸出的是Integer的value值,toString方法就是把int型別的value值轉化為string型別輸出。注意一下幾點:

①Integer的value值從 Integer.MIN_VALUE 到 Integer.MAX_VALUE 如果超出了這個範圍就會得到一些奇怪的結果。

②在Integer.MAX_VALUE基礎上加1輸出的結果是個負值。

③在Integer.MIN_VALUE基礎上加1輸出的結果是個正值。

Integer類的toString的原始碼分析

下面就深入看看Integer類的toString方法是如何實現的。還是直接上原始碼。

 public String toString() {
        return toString(value);
    }

//------------------------------

 public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
         //如果是最小值直接返回其字串因為Integer.MIN_VALUE=-2147483648 ,這樣可以節省下面計算時間
         //①
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        //獲取整數值的長度10進位制
        char[] buf = new char[size];
        //②
        getChars(i, size, buf);
        //得到整數中的每一個字元
        //③
        return new String(buf, true);
        //返回字串值
    }

上面的程式碼做幾點說明:
①如果Integer的value值正好是 Integer.MIN_VALUE 直接返回 “-2147483648” 節省時間。
②得到integer值的十進位制的長度,如果負數先求出絕對值的長度,然後再長度加1,因為負數的符號位佔一位。
③得到integer的value值的每一個字元。
④得到的字元新建字串返回。
下面看看 stringSize方法是怎樣實現的:

 static int stringSize(int x) {
        for (int i=0; ; i++)
            if (x <= sizeTable[i])
                return i+1;
                //看看x到底是幾位數
    }
//----------------------------------
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                      99999999, 999999999, Integer.MAX_VALUE };

上面的 stringSize 非常的巧妙的來整數的長度,首先定義了一個數組這個陣列中分別存放了一位十進位制的最大值,二位十進位制的最大值,依次到 Integer.MAX_VALUE為止,因為integer的最大值為 Integer.MAX_VALUE,也就是說integer的最大長度為10位。注意:
①stringSize(int x) 中的引數x 必須是正整數。
下面用一個例項看看stringSize(int x)方法。

public class IntegerDemo {
    public static void main(String[] args) {

        int x=199;

        int max=Integer.MAX_VALUE;

        int out_max=Integer.MAX_VALUE+1;

        System.out.println(stringSize(x));
        System.out.println("-------------------------");
        System.out.println(stringSize(max));
        System.out.println("-------------------------");
        System.out.println(stringSize(out_max));        
    }
     final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
         99999999, 999999999, Integer.MAX_VALUE };

     static int stringSize(int x) {
         for (int i=0; ; i++)
             if (x <= sizeTable[i])
                 return i+1;
     }
}
//輸出
/*
3
-------------------------
10
-------------------------
1
*/

上面的199小於999所以位數為3,而 out_max的值因為超過了 Integer.MAX_VALUE是個負數,得到的長度為1,因此求負數的長度時必須將其轉化為正數來求,而且求出的長度還要加上一位來放‘ - ’。得到integer的value值的長度後使用 getChars 方法來得到value的每一位上的字元。下面看看這個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 >= 65536的時候每一次獲取兩位的char值。
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));
            //使用移位操作快速計算出q*100,2^6+2^5+2^2=64+32+4=100.
            i = q;
            buf [--charPos] = DigitOnes[r];
            buf [--charPos] = DigitTens[r];
        }

        // 當 i <= 65536的時候每次只獲取一位的char值
        // assert(i <= 65536, i);
        for (;;) {
            q = (i * 52429) >>> (16+3);
            //q/10,2^19=524288, (double)52429/(1<<19)=0.10000038146972656
            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;
            //如果是負數加上符號位
        }
    }
//--------------------------------------
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 [] 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',
        } ;

getChars方法也是十分的巧妙
①在 i >= 65536 的時候每次能夠求出2位的char 值,求2位的char值時利用了兩個陣列 DigitOnes 和 DigitTens 就是在這兩個陣列的幫助下才能一次求出2位的char值下面就簡單的說一下怎樣利用這兩個陣列來分別求出個位和十位上的值的,假如現在得到兩位數是 65,個位上是 5 要得到個位上的5,這時候不管十位是多少個位上一定是5,所以陣列DigitOnes的 05,15,25,35,45,55,65,75,85,95位置上都是 5,這樣不管是25,還是35 都能得到個位上的5。在來看看如何得到十位上的數,還是65,十位是6,所以DigitTens 的60,61,62,63,64,……69 位置上都是6。

② q = (i * 52429) >>> (16+3);這段程式碼其實就是q=i/10 ,其中 (double)52429/(1<<19)=0.10000038146972656也就是在int型的時候計算一個數的十分之1的精度是夠的,可以看出jdk開發者的這種優化意識是非常強的。
③(q << 6) + (q << 5) + (q << 2) 這段等價於q*100,因為 q*2^6+q*2^5+q*2^2=q*(64+32+4)=100q.這裡為什麼不直接使用q*100,而要使用位移操作,因為其實q*100底層還是要進行位移操作的,直接使用位移操作效率更高,這也給我們程式設計帶來啟示。能夠優化的時候一定要優化。