java int轉String全部方式的效率對比與深刻解析

  在java中,你們確定都會遇到int類型轉String類型的情形,知其然知其因此然,總結加分析一下,int類型轉String類型有如下幾種方式:  javascript

  1. a+""
  2. String.valueOf(a)
  3. Integer.toString(a)
  以上三種方法在實際使用過程當中都是沒有問題的,可是效率上仍是有些許差異的,因此寫個小程序來對比一下他們的效率:

int a = 123456789;
long start = System.currentTimeMillis();
for (int i=0; i<100000; i++){
    String m = a+"";
}
long end = System.currentTimeMillis();
Log.e("time", "a+\"\" = " + (end - start));

start = System.currentTimeMillis();
for (int i=0; i<100000; i++){
    String n = String.valueOf(a);
}
end = System.currentTimeMillis();
Log.e("time", "String.valueOf(a) = " +(end-start));

start = System.currentTimeMillis();
for (int i=0; i<100000; i++){
    String n = Integer.toString(a);
}
end = System.currentTimeMillis();
Log.e("time", "Integer.toString(a) = " +(end-start));複製代碼

最後打印出來的執行時間:html

E/time: a+"" = 257
E/time: String.valueOf(a) = 140
E/time: Integer.toString(a) = 159複製代碼

能夠看到在效率上除了a+""這種方式以外,其餘兩種方式的效率差很少,爲何呢?看源碼!java

String.valueOf(a) && Integer.toString(a)

  先看看後兩種方式的源碼:
String.valueOf(a)->Integer.toString(a)->IntegralToString.intToString(a)->convertInt(null, a)git

Integer.toString(a)->IntegralToString.intToString(a)->convertInt(null, a)
能夠看到String.valueOf是經過調用Integer.toString實現的,也難怪他們的效率如此接近。他們最後都會調用到convertInt函數中:算法

private static String convertInt(AbstractStringBuilder sb, int i) {
    boolean negative = false;
    String quickResult = null;
    if (i < 0) {
        negative = true;
        i = -i;
        if (i < 100) {
            if (i < 0) {
                // If -n is still negative, n is Integer.MIN_VALUE
                quickResult = "-2147483648";
            } else {
                quickResult = SMALL_NEGATIVE_VALUES[i];
                if (quickResult == null) {
                    SMALL_NEGATIVE_VALUES[i] = quickResult =
                            i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
                }
            }
        }
    } else {
        if (i < 100) {
            quickResult = SMALL_NONNEGATIVE_VALUES[i];
            if (quickResult == null) {
                SMALL_NONNEGATIVE_VALUES[i] = quickResult =
                        i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
            }
        }
    }
    if (quickResult != null) {
        if (sb != null) {
            sb.append0(quickResult);
            return null;
        }
        return quickResult;
    }

    int bufLen = 11; // Max number of chars in result
    char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
    int cursor = bufLen;

    // Calculate digits two-at-a-time till remaining digits fit in 16 bits
    while (i >= (1 << 16)) {
        // Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
        int q = (int) ((0x51EB851FL * i) >>> 37);
        int r = i - 100*q;
        buf[--cursor] = ONES[r];
        buf[--cursor] = TENS[r];
        i = q;
    }

    // Calculate remaining digits one-at-a-time for performance
    while (i != 0) {
        // Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
        int q = (0xCCCD * i) >>> 19;
        int r = i - 10*q;
        buf[--cursor] = DIGITS[r];
        i = q;
    }

    if (negative) {
        buf[--cursor] = '-';
    }

    if (sb != null) {
        sb.append0(buf, cursor, bufLen - cursor);
        return null;
    } else {
        return new String(cursor, bufLen - cursor, buf);
    }
}複製代碼

分析一下,這個函數的工做主要能夠分爲這幾步:小程序

  1. 若是a爲負數,將a變成正數,若是a還小於0,直接置爲Integer.MIN_VALUE;若是a小於100,則直接使用TENS和ONES數組進行快速計算得出結果,加上'-'號,直接返回該結果。
  2. 若是a爲正數而且小於100,直接使用TENS和ONES數組進行快速計算得出結果返回。
  3. 若是上面兩步沒有處理完,說明a是大於100的數字,沒法直接使用TENS和ONES數組進行快速計算,處理方式就是2位爲一步循環處理,每次將這兩位使用TENS和ONES數組進行快速計算得出這兩位的結果存在數組的相應位置,直到只剩一位;最後剩下的一位使用DIGITS數組得出16進制的結果放在最後,返回結果。
  那麼問題來了,當a>=100的時候,那兩次while循環爲何會使用0x51EB851FL和0xCCCD這兩個數字呢?這個問題不要問我,我也不知道,不過源碼做者註釋寫的很明白了:
// Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
// Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
去看 《Hacker's Delight》的10-8章。
  接着還有一個問題是TENS和ONES數組,直接看代碼,一目瞭然:

/** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
private static final char[] TENS = {
        '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'
};

/** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
private static final char[] ONES = {
        '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',
};複製代碼

每一個數組都是100的長度,都是用來處理0~99這100個數字,個位和十位的處理方式也很清楚。
  從代碼角度來看,這個算法在數字小於100的和大於100的處理方式是不同的,小於100的快速計算法執行時間會遠遠短於大於100的方式,驗證一下,將a變量修改成10:api

E/time: i+"" = 199
E/time: String.valueOf() = 7
E/time: Integer.toString() = 6複製代碼

確實短了不少!!!數組

a+""

  再來看看a+""的方式,我認可這種方式我用的最多了,由於太簡單了,java源碼對'+'運算符進行了重載,源碼我找不到啊,不過從網上找一些資料:oracle

The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method. String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java. For additional information on string concatenation and conversion, see Gosling, Joy, and Steele, The Java Language Specification.app

地址:docs.oracle.com/javase/6/do…

能夠看到,'+'運算符的主要方式是使用StringBuilder或者StringBuffer來實現的,相似於:

StringBuilder sb = new StringBuilder();
sb.append("");
sb.append(i);
String strI = sb.toString();複製代碼

再來看看append的源碼:
StringBuffer.append->IntegralToString.appendInt(this, a)->convertInt(sb, i)
能夠看到'+'運算符最後也是調用到了同一個函數,只不過第一個參數的sb不爲null而已,因此已經很清楚了,'+'運算符的執行效率不高的緣由應該就在以前的new StringBuilder等操做和以後的StringBuilder.toString等操做,反編譯class文件也能夠得出同樣的結論:
stackoverflow.com/a/4105406
  因此a+""的方式之後就少用一點了,效率不高,也顯得不太專業。

擴展

  String 擴展的相關知識:
常量池的內存分配在 JDK六、七、8中有不一樣的實現:

  1. JDK6及以前版本中,常量池的內存在永久代PermGen進行分配,因此常量池會受到PermGen內存大小的限制。
  2. JDK7中,常量池的內存在Java堆上進行分配,意味着常量池不受固定大小的限制了。
  3. JDK8中,虛擬機團隊移除了永久代PermGen。
    關於永久代移除:www.infoq.com/cn/articles…
    例子1:
    public class StringTest {
     public static void main(String[] args) {
         String a = "java";
         String b = "java";
         String c = "ja" + "va";
     }
    }複製代碼
    變量 a、b 和 c 都指向常量池的 「java」 字符串,表達式 「ja」 + 「va」 在編譯期間會把結果值」java」直接賦值給c,因此最終的結果 a==c 爲 true。

例子2:

public class StringTest {
    public static void main(String[] args) {
        String a = "hello ";
        String b = "world";
        String c = a + b;
        String d = "hello world";
    }
}複製代碼

咱們根據上面知道在 java 中 「+」 運算符其實是使用 StringBuilder.append 去實現的,因此此時會在 Java 堆上新建一個 String 對象,這個 String 對象最終指向常量池的 "hello world",因此說此時 c==d 爲 false。
不過有種特殊狀況,當final修飾的變量發生鏈接動做時,編譯器會進行優化,將表達式結果直接賦值給目標變量:

public class StringTest {
    public static void main(String[] args) {
        final String a = "hello ";
        final String b = "world";
        String c = a + b;
        String d = "hello world";
    }
}複製代碼

因此此時 c==d 爲 true。

引用

www.importnew.com/21711.html
www.importnew.com/21720.html

相關文章
相關標籤/搜索