1. 程式人生 > >Java中substring、split、StringTokenizer三種擷取字串方法的效能比較

Java中substring、split、StringTokenizer三種擷取字串方法的效能比較

最近在閱讀java.lang下的原始碼,讀到String時,突然想起面試的時候曾經被人問過:都知道在大資料量情況下,使用String的split擷取字串效率很低,有想過用其他的方法替代嗎?用什麼替代?我當時的回答很斬釘截鐵:沒有。 
Google了一下,發現有2種替代方法,於是在這裡我將對這三種方式進行測試。 
測試的軟體環境為:Windows 10、Intellij IDEA、JDK1.8。 
測試用例使用類ip形式的字串,即3位一組,使用”.”間隔。資料分別使用:5組、10組、100組、1000組、10000組、100000組。

實現

閒話不說,先上程式碼:

import java.util.Random;
import java.util.StringTokenizer;

/**
 * String測試類
 * @author yuitang
 *
 */
public class StringTest { public static void main(String args[]){ String orginStr = getOriginStr(5); //////////////String.split()表現////////////////////////////////////////////// System.out.println("使用String.split()切分字串"); long st1 = System.nanoTime(); String [] result = orginStr.split("\\."); System.out.println("String.split()擷取字串用時:"
+ (System.nanoTime()-st1)); System.out.println("String.split()擷取字串結果個數:" + result.length); System.out.println(); //////////////StringTokenizer表現////////////////////////////////////////////// System.out.println("使用StringTokenizer切分字串"); long st3 = System.nanoTime(); StringTokenizer token=new StringTokenizer(orginStr,"."
); System.out.println("StringTokenizer擷取字串用時:"+(System.nanoTime()-st3)); System.out.println("StringTokenizer擷取字串結果個數:" + token.countTokens()); System.out.println(); ////////////////////String.substring()表現////////////////////////////////////////// long st5 = System.nanoTime(); int len = orginStr.lastIndexOf("."); System.out.println("使用String.substring()切分字串"); int k=0,count=0; for (int i = 0; i <= len; i++) { if(orginStr.substring(i, i+1).equals(".")){ if(count==0){ orginStr.substring(0, i); }else{ orginStr.substring(k+1, i); if(i == len){ orginStr.substring(len+1, orginStr.length()); } } k=i;count++; } } System.out.println("String.substring()擷取字串用時:"+(System.nanoTime()-st5)); System.out.println("String.substring()擷取字串結果個數:" + (count + 1)); } /** * 構造目標字串 * eg:10.123.12.154.154 * @param len 目標字串組數(每組由3個隨機陣列成) * @return */ private static String getOriginStr(int len){ StringBuffer sb = new StringBuffer(); StringBuffer result = new StringBuffer(); Random random = new Random(); for(int i = 0; i < len; i++){ sb.append(random.nextInt(9)).append(random.nextInt(9)).append(random.nextInt(9)); result.append(sb.toString()); sb.delete(0, sb.length()); if(i != len-1) result.append("."); } return result.toString(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

改變目標資料長度修改getOriginStr的len引數即可。 
5組測試資料結果如下圖:

Function510100100010000100000
split45792501341555341035446539513623128381
StringTokenizer240802487041845627664460830002
substring3552837896156324838463857529325692324

下面這張圖對比了下,split耗時為substring和StringTokenizer耗時的倍數:

TablessplitStringTokenizersubstring
51.901.29
102.021.32
1003.170.99
100016.501.23
10000120.950.63
100000770.890.90

結論

最終,StringTokenizer在擷取字串中效率最高,不論資料量大小,幾乎持平。substring和split的效率幾乎差別不大,甚至當資料量足夠龐大的時候,substring的效率還比不上split。 
究其原因,split的實現方式是採用正則表示式實現,所以其效能會比較低。至於正則表示式為何低,還未去驗證。split原始碼如下:

public String[] split(String regex) {
    return split(regex, 0);
}

public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
     (1)one-char String and this character is not one of the
        RegEx's meta characters ".$|()[{^?*+\\", or
     (2)two-char String and the first char is the backslash and
        the second is not the ascii digit or ascii letter.
     */
    char ch = 0;
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {    // last one
                //assert (list.size() == limit - 1);
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // If no match was found, return this
        if (off == 0)
            return new String[]{this};

        // Add remaining segment
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
}