文中相關源碼:java
Long.javagit
上一篇文章 走進 JDK 之 Integer 解析了 Integer.java
,而 Long.java
和 Integer.java
的源碼結構幾乎是如出一轍的,因此這篇文章會寫的比較簡略,沒有細讀過 Integer.java
源碼的能夠先看一下個人上一篇文章。這裏就簡單介紹一下 Long
以及源碼中和 Integer
的細微區別。github
public final class Long extends Number implements Comparable<Long>{}
複製代碼
和 Integer
同樣,不可變類,繼承了 Number
類,實現了 Comparable
接口。數組
// long 最小值,-2^63
public static final long MIN_VALUE = 0x8000000000000000L;
// long 最大值,-2^63-1
public static final long MAX_VALUE = 0x7fffffffffffffffL;
public static final Class<Long> TYPE = (Class<Long>) Class.getPrimitiveClass("long");
private final long value;
// 以二進制補碼形式表示 long 值所需的比特數
public static final int SIZE = 64;
// 以二進制補碼形式表示 long 值所需的字節數,恆爲 8
public static final int BYTES = SIZE / Byte.SIZE;
private static final long serialVersionUID = 4290774380558885855L;
複製代碼
public Long(long value) {
this.value = value;
}
public Long(String s) throws NumberFormatException {
this.value = parseLong(s, 10);
}
複製代碼
構造函數和 Integer
同樣也是兩個。第一個參數爲 long
值。第二個參數爲 String
字符串,經過 parseLong()
方法轉換爲 long
值。緩存
Long.java
中的方法實現幾乎和 Integer.java
一致,這裏再也不一一分析源碼了,僅列舉一下出現的方法,也算對上一篇文章的總結。bash
public static int parseLong(String s) public static long parseLong(String s, int radix) public static long parseUnsignedLong(String s) public static long parseUnsignedLong(String s, int radix) public static Long decode(String nm) public static Long valueOf(String s) public static Long valueOf(String s, int radix) public static Long getLong(String nm) public static Long getLong(String nm, long val) public static Long getLong(String nm, Long val) 複製代碼
若是你還記得 IntegerCache
的話,沒錯,一樣也有一個 LongCache
:微信
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
複製代碼
和 IntegerCache
同樣,它也是緩存了 -128
到 127
。函數
public static String toString(long i) public static String toString(long i, int radix) public static String toUnsignedString(long i) public static String toUnsignedString(long i, int radix) 複製代碼
首先看一下 toString(long i)
方法:post
public static String toString(long i) {
if (i == Long.MIN_VALUE)
return "-9223372036854775808";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
複製代碼
總體套路和 Integer
是同樣的,但在 stringSize()
和 getChars()
的具體實現上有一些細微的不一樣。ui
static int stringSize(long x) {
long p = 10;
for (int i=1; i<19; i++) {
if (x < p)
return i;
p = 10*p;
}
return 19;
}
複製代碼
Long.MAX_VALUE
是 19 位數字,因此最多不會循環超過 19 次。原理和 Integer
的 stringSize()
方法是同樣的,小於 10
就是 1
位數,小於 100
就是 2
位數..... 。還記得 Integer
中是怎麼實現的嗎?
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;
}
複製代碼
使用了一個 10
個元素的數組。若是 Long
也使用一樣的方法,那麼就須要一個 20
個元素的數組。JDK 開發者多是站在考慮內存使用的角度,並無使用這種方法。
除了 stringSize()
外,getChars()
的實現也略有不一樣。Long
中的 getChars()
以下所示:
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
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
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);
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;
}
}
複製代碼
這裏分隔了三段循環體,分別以 Integer.MAX_VALUE
和 65536
爲分界線,而 Integer
僅以 65536
爲界分了兩段循環體。咱們先不看這多出來的一段,來看一下 Long
裏面第二段循環體的實現:
// Get 2 digits/iteration using ints
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];
}
複製代碼
看似和 Integer
同樣,但別忘了這裏是 Long
。Long
佔用 8
個字節,而 int
只佔用 4
個字節。因此,當 long
型的值小於 Integer.MAX_VALUE
時,能夠強轉爲 int
, 從而節省內存。看到這,你應該明白第一個循環體的含義了,當 i > Integer.MAX_VALUE
時,強轉 int
會溢出,就只能當作 long
處理了。
Integer.toUnsignedString(int,int)
public static String toUnsignedString(int i, int radix) {
return Long.toUnsignedString(toUnsignedLong(i), radix);
}
複製代碼
上面是 Integer
中 toUnsignedString()
的實現, 直接調用 Long
的相應方法。來看一下 Long
是如何實現這個方法的:
public static String toUnsignedString(long i, int radix) {
if (i >= 0)
return toString(i, radix);
else {
switch (radix) {
case 2:
return toBinaryString(i);
case 4:
return toUnsignedString0(i, 2);
case 8:
return toOctalString(i);
case 10:
/* * We can get the effect of an unsigned division by 10 * on a long value by first shifting right, yielding a * positive value, and then dividing by 5. This * allows the last digit and preceding digits to be * isolated more quickly than by an initial conversion * to BigInteger. */
long quot = (i >>> 1) / 5;
long rem = i - quot * 10;
return toString(quot) + rem;
case 16:
return toHexString(i);
case 32:
return toUnsignedString0(i, 5);
default:
return toUnsignedBigInteger(i).toString(radix);
}
}
}
複製代碼
把 long
型數值轉換爲無符號字符串。long
的最大值是 2^63-1
,而 unsigned long
最大值爲 2^64-1
。因此正整數對於 toUnsignedString()
方法來講,仍能夠當作有符號數處理,直接調用 toString(long i,int radix)
處理。
對於負數來講,根據進制的不一樣,採起不一樣的處理。radix
爲 2
、4
、8
、16
、32
時,分別調用了 toBinaryString()
、toUnsignedString0()
、toOctalString()
、toHexString()
、toUnsignedString0()
方法,其實這些方法最後都調用了 toUnsignedString0(long i,int shift)
方法,其中 raidx = 1 << shift
:
static String toUnsignedString0(long val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Long.SIZE - Long.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedLong(val, shift, buf, 0, chars);
return new String(buf, true);
}
複製代碼
逐行分析一下:
int mag = Long.SIZE - Long.numberOfLeadingZeros(val);
複製代碼
mag
表示該數字表示爲二進制實際須要的位數(去除高位的 0)。numberOfLeadingZeros()
方法以前說過,表示該數字以二進制補碼形式表示時最高位 1 前面的位數。好比 16L
,二進制爲 0001 0000
, 最高位 1 在第 4
位,則前面有 59
個 0,表示 16L
實際須要的位數是 Long.SIZE - 59 = 5
,即 10000
。固然,對於負數來講,符號位爲 1
,因此永遠須要 64
位來表示。
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
複製代碼
根據 mag
和 shift
獲取要表示的字符串的字符數。仍以上面的 16L
爲例,mag
等於 5,若 shift
爲 1
,計算出 chars
等於 5。若 shift
等於 4
,即以十六進制表示,計算出 chars
等於 2,由於 16
對應的十六進制是 0x10
,表示爲字符的話只須要兩個字符。
static int formatUnsignedLong(long val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift; // 進制
int mask = radix - 1; // 掩碼,二進制都爲 1
do {
buf[offset + --charPos] = Integer.digits[((int) val) & mask]; // 取出 val 中對應進制的最後一位
val >>>= shift; // 無符號右移 shift,高位補 0,移走上一步已經取出的最後一位數據
} while (val != 0 && charPos > 0);
return charPos;
}
複製代碼
逐行分析:
int charPos = len;
複製代碼
charPos
表示填充字符在數組中的位置。初始值爲 len
,不難想到仍是從數組尾部開始填充的,因此循環體中的內容確定是取出 unsigned val
的最後一位數字。
int radix = 1 << shift; // 進制
int mask = radix - 1; // 掩碼,二進制都爲 1
複製代碼
這裏先記住一點,掩碼 mask
的二進制有效數字都是 1
。
二進制 : mask = 1 = 0b0001
八進制 : mask = 7 = 0b0111
十六進制 : mask = 15 = 0b1111
複製代碼
最後看循環體中的內容:
do {
buf[offset + --charPos] = Integer.digits[((int) val) & mask]; // 取出 val 中對應進制的最後一位
val >>>= shift; // 無符號右移 shift,高位補 0,移走上一步已經取出的最後一位數據
} while (val != 0 && charPos > 0);
複製代碼
因爲掩碼 mask
的特殊性,((int) val) & mask
一定是 val
轉換成對應進制中的最後一位數字,將其塞入字符數組中。而後執行 val >>>=shift
,無符號右移都是高位填 0,移動 shift
位,正好移除了對應進制的最後一位數。以此循環,直到所有填 0,字符數組也就填滿了。
以 31L
爲例,其二進制表示爲 0001 1111
,十六進制表示爲 0x1f
,將其轉換爲十六進制字符串應該爲 1f
,其中 radix
爲 16
,shift
爲 4
,mask
爲 15
。
第一次循環,((int) val) & mask
等於 0001 1111 & 0000 1111
, 值爲 1111
,對應 Integer.digits
中字符爲 'f'
。而後將 val
無符號右移四位,獲得新的值爲 0000 0001
,進入第二次循環。
第二次循環,val
與 mask
與運算,0000 0001 & 0000 1111
,值爲 0001
,對應字符爲 '1'
。再將 val
無符號右移四位,其值爲 0
,結束循環。
這樣就獲得最後的無符號十六進制字符串爲 '1f'
。
toUnsignedString(long value,int radix)
對十進制進行了單獨處理:
case 10:
/* * We can get the effect of an unsigned division by 10 * on a long value by first shifting right, yielding a * positive value, and then dividing by 5. This * allows the last digit and preceding digits to be * isolated more quickly than by an initial conversion * to BigInteger. */
long quot = (i >>> 1) / 5;
long rem = i - quot * 10;
return toString(quot) + rem;
複製代碼
註釋的大概意思是,咱們能夠將數字先無符號右移一位獲得一個正值,再除以 5
, 以此來代替用無符號數除以 10
。這樣能夠比經過初始化 BigInteger
更快的分離出最後一個數字。粗看代碼,大概步驟應該是將 i
分紅一個 long
值 quot
和最後一個數字 rem
,對於 quot
實際上是有符號的,直接調用 toString()
方法,最後和 rem
拼接起來便可。大體思路是這樣,其中具體的位運算尚未看的太懂。
default:
return toUnsignedBigInteger(i).toString(radix);
複製代碼
對於其餘進制,一概經過 BigInteger
進行處理,這裏不展開分析,後面說道 BigInteger
時再說。
位運算和 Integer
是徹底一致的。
long highestOneBit(long i) : 返回以二進制補碼形式,取左邊最高位 1,後面所有填 0 表示的 long 值 long lowestOneBit(long i) : 與 highestOneBit() 相反,取其二進制補碼的右邊最低位 1,其他填 0 int numberOfLeadingZeros(long i) : 返回左邊最高位 1 以前的 0 的個數 int numberOfTrailingZeros(long i): 返回右邊最低位 1 以後的 0 的個數 int bitCount(long i) : 二進制補碼中 1 的個數 long rotateRight(long i, int distance) : 將 i 的二進制補碼循環右移 distance(注意與普通右移不一樣的是,右移的數字會移到最左邊) long rotateLeft(long i, int distance) : 與 rotateRight 相反 long reverse(long i) : 反轉二進制補碼 int signum(long i) : 正數返回 1,負數返回 -1,0 返回 0 long reverseBytes(long i) : 以字節爲單位反轉二進制補碼 複製代碼
複習了 Intger
中的內容,補充了 Integer
中沒提到的 toUnsignedString()
方法。關於整數差很少就說到這了,後面有機會再分析一下 BigInteger
。下篇文章來講說 Float
,如今不妨思考這樣一個問題,0.3f - 0.2f
等於多少,寫下你的答案,再打開 IDEA 運行一下吧!
文章同步更新於微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!