走進 JDK 之 Long

文中相關源碼:java

Long.javagit

上一篇文章 走進 JDK 之 Integer 解析了 Integer.java,而 Long.javaInteger.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

String 轉 long

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 同樣,它也是緩存了 -128127函數

int 轉 String

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()

首先看一下 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 次。原理和 IntegerstringSize() 方法是同樣的,小於 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_VALUE65536 爲分界線,而 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 同樣,但別忘了這裏是 LongLong 佔用 8 個字節,而 int 只佔用 4個字節。因此,當 long 型的值小於 Integer.MAX_VALUE 時,能夠強轉爲 int, 從而節省內存。看到這,你應該明白第一個循環體的含義了,當 i > Integer.MAX_VALUE 時,強轉 int 會溢出,就只能當作 long 處理了。

toUnsignedString()

Integer.toUnsignedString(int,int)

public static String toUnsignedString(int i, int radix) {
    return Long.toUnsignedString(toUnsignedLong(i), radix);
}
複製代碼

上面是 IntegertoUnsignedString() 的實現, 直接調用 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) 處理。

2、4、8、十六進制

對於負數來講,根據進制的不一樣,採起不一樣的處理。radix2481632時,分別調用了 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);
複製代碼

根據 magshift 獲取要表示的字符串的字符數。仍以上面的 16L 爲例,mag 等於 5,若 shift1,計算出 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,其中 radix16shift4mask15

  • 第一次循環,((int) val) & mask 等於 0001 1111 & 0000 1111, 值爲 1111,對應 Integer.digits 中字符爲 'f'。而後將 val 無符號右移四位,獲得新的值爲 0000 0001,進入第二次循環。

  • 第二次循環,valmask 與運算,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 分紅一個 longquot 和最後一個數字 rem,對於 quot 實際上是有符號的,直接調用 toString() 方法,最後和 rem 拼接起來便可。大體思路是這樣,其中具體的位運算尚未看的太懂。

其餘進制

default:
    return toUnsignedBigInteger(i).toString(radix);
複製代碼

對於其餘進制,一概經過 BigInteger 進行處理,這裏不展開分析,後面說道 BigInteger 時再說。

位運算

位運算和 Integer 是徹底一致的。

long highestOneBit(long i) : 返回以二進制補碼形式,取左邊最高位 1,後面所有填 0 表示的 longlong 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 題解,歡迎關注!

相關文章
相關標籤/搜索