文章看事後感受受益不淺,因此留下了以備溫故:http://www.congmo.net/blog/2012/03/05/Long-toString/ java
陸陸續續花了近兩週時間看完了Long.java,能夠說收穫頗豐。也花了幾天時間構思應該如何去寫出來,苦於一直沒有好的思路,又不能在這裏乾耗着浪費時間。因此就準備寫出來了。很隨意的寫,想到哪裏寫到哪裏。準備貼不少源碼,附加我我的的理解。 git
首先讓咱們目擊下Long中強大的toString方法。 數組
public static String toString(long i, int radix) { if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) radix = 10; if (radix == 10) return toString(i); char[] buf = new char[65]; int charPos = 64; boolean negative = (i < 0); if (!negative) { i = -i; } while (i <= -radix) { buf[charPos--] = Integer.digits[(int)(-(i % radix))]; i = i / radix; } buf[charPos] = Integer.digits[(int)(-i)]; if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (65 - charPos)); }
第二個參數radix是進制數,範圍是:2-36.你們都知道1進制沒什麼意義,因此進制數從2開始,我一直對36存在疑問,雖然它是0-9和a-z一共 36個字符組成,因此最大進制數定義爲36.可是還有大寫字母啊,還有26個呢,這樣就能夠定義最大62進制。也許有什麼淵源在這裏我不知道,我在 OSChina上也發過問,可是都沒有讓我很滿意的答案。 函數
radix若是不在2-36範圍內,則默認10進制。而若是是10進制,Long有專門將10進制的Long轉化爲String的toString方法,稍後再說。 ui
非10進制的toString就在這裏負責處理。 spa
char[] buf = new char[65];
if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (65 - charPos));
清楚了吧,這個buf0若是有值,那麼只多是」-「,不會是其餘任何值。最後一行也很好理解,用了幾位,那麼就傳給String構造函數幾位。因此上面說的」正數只用64位」是不嚴謹的。 .net
我喜歡這裏negative的用法,算不上很巧妙,可是避開了正數和負數的差別。 設計
if (!negative) { i = -i; }
while (i <= -radix) { buf[charPos--] = Integer.digits[(int)(-(i % radix))]; i = i / radix; } buf[charPos] = Integer.digits[(int)(-i)];
請看while內部與外部,設想若是i是正是負未知,那麼這兩處就無法統一使用-(i % radix)和-i了。因此將i提早轉換爲負值了。 Integer.digits是個挺巧妙的東西,能夠隨意應付2-36進制的轉換。它是這樣定義的: Liquid error: Flag value is invalid: -O 」」 好比說,radix = 2,那麼i % radix只能爲0或者1,對應digits0或者digits1,依此類推。 code
我想這個toString已經介紹的夠詳細了。另外,源碼中將i轉換爲負數,那麼轉換爲正數也確定成立吧。因而我作了一點點改動: blog
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' }; public static String toString(long i, int radix) { if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) radix = 10; if (radix == 10) return "";//不是重點,直接跳過 char[] buf = new char[65]; int charPos = 64; boolean negative = (i < 0); if (negative) { i = -i; } while (i >= radix) { buf[charPos--] = digits[(int)((i % radix))]; i = i / radix; } buf[charPos] = digits[(int)(i)]; if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (65 - charPos)); }
System.out.println(Long.toString(-8L, 2)); System.out.println(toString(-8L,2));
輸出結果: -1000 -1000
這個toString方法用於將參數i轉化爲十進制形式的字符串,toString(long i)自己是很簡單的,核心是getChars(long i, int index, char buf)。那麼就一塊兒目擊下它們都是如何實現的。
public static String toString(long i) { if (i == Long.MIN_VALUE) eturn "-9223372036854775808"; int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); char[] buf = new char[size]; getChars(i, size, buf); return new String(0, size, buf); }
static void getChars(long i, int index, char[] buf) { long q; int r; int charPos = index; char sign = 0; if (i < 0) { sign = '-'; i = -i; } // Get 2 digits/iteration using longs until quotient fits into an int //8-4字節 while (i > Integer.MAX_VALUE) { q = i / 100; // really: r = i - (q * 100); r = (int)(i - ((q << 6) + (q << 5) + (q << 2))); i = q; buf[--charPos] = Integer.DigitOnes[r]; buf[--charPos] = Integer.DigitTens[r]; } // Get 2 digits/iteration using ints //4-2字節 int q2; int i2 = (int)i; while (i2 >= 65536) { q2 = i2 / 100; // really: r = i2 - (q * 100); r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); i2 = q2; buf[--charPos] = Integer.DigitOnes[r]; buf[--charPos] = Integer.DigitTens[r]; } // Fall thru to fast mode for smaller numbers // assert(i2 <= 65536, i2); //2-0字節 for (;;) { q2 = (i2 * 52429) >>> (16+3); r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... buf[--charPos] = Integer.digits[r]; i2 = q2; if (i2 == 0) break; } if (sign != 0) { buf[--charPos] = sign; } }
這段的主要目的是通過它的處理以後,能將一個long類型的數字轉換成int來處理。這段while中幾乎每行都是經典,首先這行:r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));很巧妙的使用高效的位移運算完成了r = i - (q * 100)。其實這樣作事爲了獲得i的最後兩位,好比2147483649這樣一個long,通過這步以後r = 49.
隨後更巧妙的一件事就又發生了。那就是Integer中的DigitOnes和DigitTens巧妙的設計。那就看看這兩個數組是如何巧妙的。
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', } ; 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', } ;
經過這兩個精巧的數組,巧妙的將兩位數字轉化爲十位和個位,好比49用這兩個數組表示就是DigitTens49=4和DigitOnes49=9。我真心喜歡這個設計。這樣也就很是簡單的就將一個兩位數一位一位放入char數組中了。 再次強調q * 100的實現方式,100 = 64 + 32 + 4,經過位移q再相加,完美實現q * 100。 直到這個long能夠被int表示時,轉入下一段。
這段代碼的處理方式幾乎同上一段是如出一轍的,區別在於這裏處理的是int而不是long。
我一直都特別奇怪爲何要在2個字節這個點分割呢!咱們稍後在小結裏面描述,除了這點,還有一處亮點:q2 = (i2 * 52429) >>> (16+3); 這個但是讓我困惑了很久很久的。後來無心中在javaeye上搜到一篇帖子上揭露了這個美麗的亮點,52429/524288 = 0.10000038146972656, 524288 = 1 << 19,換句話說q2 = (i2 * 52429) >>> (16+3);就是q2 = i2/10爲了不效率低下的除法,換用了這種方式實現除法,真是絕啊!
總結一下getChars這個絕妙的方法,之因此分紅3段,是由於JVM的實現中,int的效率最高,long的效率很低,因此第一步就將long轉換成 int,再進行處理。而後呢,爲了不除法,並且乘以52429以後能夠被int表示,不會溢出,因此就出現了2字節這個分割點。總之 呢,toString(long i)方法絕對是個絕妙的方法啊。裏面有許多值得借鑑的地方。
接下來讓咱們認識下toUnsignedString(long i, int shift),這個方法一樣巧妙,一個方法就把long轉二進制,八進制,十六進制所有搞定。僅僅經過shift一個參數,一樣是經過位移來實現的。好比 八進制,那麼shift就是3,而後經過1 >> 3實現。惟一一個限制就是隻能表示進制數是2的n次冪。
private static String toUnsignedString(long i, int shift) { char[] buf = new char[64]; int charPos = 64; int radix = 1 << shift; long mask = radix - 1; do { buf[--charPos] = Integer.digits[(int)(i & mask)]; i >>>= shift; } while (i != 0); return new String(buf, charPos, (64 - charPos)); }
首先經過int radix = 1 << shift;實現進制數的轉換,
隨後就是一個精心的設計,long mask = radix -1; 爲何要有這樣一個值呢?實際上是這樣的,radix是2的n次冪,減1以後就是全1了,好比8-1的二進制就是111,其餘同理。而後i & mask就取到進制數對應二進制的位數。好比十六進制的mask = 15,對應的二進制爲1111,i & mask就是取i對應二進制的後四位。再從Integer.digits中取得對應進制數的值。
最後,再經過i >>>= shift; 將已經取得的位數移除掉,直至i=0爲止。
這樣Long中的toString方法簇就解析完畢。總之一句話,Long就像一座寶庫,每走一步都是金子。期待下一桶金子吧。