2012-06-12 64 views
29

這裏是我的應用程序當前代碼:的Java分割字符串表演

String[] ids = str.split("/"); 

當分析應用程序,我注意到,非可忽略的時間都花在了分裂的字符串。

我還了解到split實際上需要一個正則表達式,這在我這裏是沒用的。

所以我的問題是,我可以使用什麼替代方法來優化字符串拆分?我看過StringUtils.split,但速度更快嗎?

(我會一直嘗試和測試自己,但剖析我的應用程序需要花費大量的時間,所以如果有人已經知道了答案那是節省一些時間)

回答

32

String.split(String)如果您的模式只有一個字符長度,則不會創建正則表達式。當用單個字符分割時,它將使用相當高效的專用代碼。在這種特殊情況下,StringTokenizer速度並不快。

這是在OpenJDK7/OracleJDK7中引入的。 Here's a bug reporta commit。我做了一個simple benchmark here


$ java -version 
java version "1.8.0_20" 
Java(TM) SE Runtime Environment (build 1.8.0_20-b26) 
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode) 

$ java Split 
split_banthar: 1231 
split_tskuzzy: 1464 
split_tskuzzy2: 1742 
string.split: 1291 
StringTokenizer: 1517 
+1

感謝這個基準。你的代碼是「不公平的」,雖然StringTokenizer部分避免了創建一個List並將其轉換爲一個數組......儘管起點不錯! –

7

StringTokenizer快得多簡單的解析像這樣(我一段時間後做了一些基準測試,你會得到巨大的加速)。

StringTokenizer st = new StringTokenizer("1/2/3","/"); 
String[] arr = st.countTokens(); 
arr[0] = st.nextToken(); 

如果你想伊克出多一點的表現,你可以手動做得一樣好:

String s = "1/2/3" 
char[] c = s.toCharArray(); 
LinkedList<String> ll = new LinkedList<String>(); 
int index = 0; 

for(int i=0;i<c.length;i++) { 
    if(c[i] == '/') { 
     ll.add(s.substring(index,i)); 
     index = i+1; 
    } 
} 

String[] arr = ll.size(); 
Iterator<String> iter = ll.iterator(); 
index = 0; 

for(index = 0; iter.hasNext(); index++) 
    arr[index++] = iter.next(); 
+4

的StringTokenizer是傳統類,它是由於兼容性原因而保留,儘管在新代碼中不鼓勵使用它。建議任何尋求此功能的人使用String或java.util.regex包的拆分方法。 –

+3

僅僅因爲這是遺產並不意味着它沒有用。事實上,這個特殊的課程實際上對提高性能非常有用,所以我實際上反對這個「傳統」標籤。 – tskuzzy

+3

「String」和「java.util.regex」包的拆分方法會導致使用正則表達式的巨大開銷。 'StringTokenizer'不。 –

3

java.util.StringTokenizer(String str, String delim)大約快兩倍,根據this post

但是,除非您的應用程序的規模很大,否則split應該適合您(c.f.同一文章,它在幾個毫秒內引用數千個字符串)。

+0

它不需要大規模的應用程序,在文件解析器等緊密循環中進行拆分就足夠了 - 並且頻繁 - 可以考慮解析twitterlinks,電子郵件,hashtags的典型例程....它們以Mb文本解析。例程本身可以有幾十行,但每秒會被調用數百次。 – rupps

15

如果您可以使用第三方庫,當您不需要它時,Guava'sSplitter不會產生正則表達式的開銷,並且作爲一般規則非常快。 (披露:我有助於番石榴。)

Iterable<String> split = Splitter.on('/').split(string); 

(另外,Splitter是作爲一項規則much more predictableString.split。)

+1

這對我來說是一個非常顯着的區別,同時在大文件中使用它。 –

+2

這篇文章推薦不使用Iterable,即使是Guava的團隊負責人也這樣說... http://alexruiz.developerblogs.com/?p = 2519 – sirvon

1

番石榴具有Splitter這是更靈活,所述String.split()方法,並且不(必然)使用正則表達式。 OTOH,String.split()已經在Java 7中進行了優化,以避免如果分隔符是單個字符時使用正則表達式機制。因此Java 7中的性能應該類似。

+0

噢好的我正在使用Java 5(不幸的是,無法更改那) –

0

字符串的拆分方法可能是更安全的選擇。 As of at least java 6(儘管api參考是7)他們基本上說不鼓勵使用StringTokenizer。他們的措辭在下面引用。

StringTokenizer是一個遺留類,爲了兼容性的原因,儘管在新代碼中不鼓勵使用它,但建議任何尋求該功能的人都使用String或java.util.regex包的split方法。

2

的StringTokenizer比其他任何分裂法快,但得到記號賦予到帶標記的字符串一起返回分隔符提高了像50%的性能。這是通過使用構造函數java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)來實現的。這裏有一些關於此事的其他見解:Performance of StringTokenizer class vs. split method in Java

0

你可以自己編寫分割函數,這將是最快的。 這裏是證明它的鏈接,它的工作對我來說,通過6X優化我的代碼

StringTokenizer - reading lines with integers

斯普利特:366ms 的IndexOf:50ms的 的StringTokenizer:89ms GuavaSplit:109ms IndexOf2(一些超級優化在對上述問題給出)解決方案:14MS CsvMapperSplit(按行映射行):326ms CsvMapperSplit_DOC(建立一個文檔並映射一氣呵成的所有行):177ms

2

看到,因爲我是在大規模的工作,餘噸它有助於提供更多的基準測試,包括一些我自己的實現(我分割空間,但這應該說明通常需要多長時間):

我正在使用426 MB文件, 2622761行。唯一的空格是普通空格(「」)和行(「\ n」)。

首先,我空格替換所有線路,以及基準解析一個陣容龐大:

.split(" ") 
Cumulative time: 31.431366952 seconds 

.split("\s") 
Cumulative time: 52.948729489 seconds 

splitStringChArray() 
Cumulative time: 38.721338004 seconds 

splitStringChList() 
Cumulative time: 12.716065893 seconds 

splitStringCodes() 
Cumulative time: 1 minutes, 21.349029036000005 seconds 

splitStringCharCodes() 
Cumulative time: 23.459840685 seconds 

StringTokenizer 
Cumulative time: 1 minutes, 11.501686094999997 seconds 

我然後用線(這意味着功能和環路一次做過很多次,而不是所有的基準分割線):

.split(" ") 
Cumulative time: 3.809014174 seconds 

.split("\s") 
Cumulative time: 7.906730124 seconds 

splitStringChArray() 
Cumulative time: 4.06576739 seconds 

splitStringChList() 
Cumulative time: 2.857809996 seconds 

Bonus: splitStringChList(), but creating a new StringBuilder every time (the average difference is actually more like .42 seconds): 
Cumulative time: 3.82026621 seconds 

splitStringCodes() 
Cumulative time: 11.730249921 seconds 

splitStringCharCodes() 
Cumulative time: 6.995555826 seconds 

StringTokenizer 
Cumulative time: 4.500008172 seconds 

下面是代碼:

// Use a char array, and count the number of instances first. 
public static String[] splitStringChArray(String str, StringBuilder sb) { 
    char[] strArray = str.toCharArray(); 
    int count = 0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      count++; 
     } 
    } 
    String[] splitArray = new String[count+1]; 
    int i=0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      splitArray[i] = sb.toString(); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(c); 
     } 
    } 
    return splitArray; 
} 

// Use a char array but create an ArrayList, and don't count beforehand. 
public static ArrayList<String> splitStringChList(String str, StringBuilder sb) { 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    char[] strArray = str.toCharArray(); 
    int i=0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(c); 
     } 
    } 
    return words; 
} 

// Using an iterator through code points and returning an ArrayList. 
public static ArrayList<String> splitStringCodes(String str) { 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    IntStream is = str.codePoints(); 
    OfInt it = is.iterator(); 
    int cp; 
    StringBuilder sb = new StringBuilder(); 
    while (it.hasNext()) { 
     cp = it.next(); 
     if (cp == 32) { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(cp); 
     } 
    } 

    return words; 
} 

// This one is for compatibility with supplementary or surrogate characters (by using Character.codePointAt()) 
public static ArrayList<String> splitStringCharCodes(String str, StringBuilder sb) { 
    char[] strArray = str.toCharArray(); 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    int cp; 
    int len = strArray.length; 
    for (int i=0; i<len; i++) { 
     cp = Character.codePointAt(strArray, i); 
     if (cp == ' ') { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(cp); 
     } 
    } 

    return words; 
} 

這是怎麼了我用的StringTokenizer:

StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString()); 
    words = new String[tokenizer.countTokens()]; 
    int i = 0; 
    while (tokenizer.hasMoreTokens()) { 
     words[i] = tokenizer.nextToken(); 
     i++; 
    } 
0

使用Apache Commons Lang中»3.0的

StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"] 

如果您需要非正則表達式拆分,並希望在String數組的結果,然後使用StringUtils的,我比較StringUtils.splitByWholeSeparator與番石榴的Splitter和Java的String分裂,發現StringUtils更快。

  1. StringUtils的 - 8ms的
  2. 字符串 - 11個毫秒
  3. 分路器 - 1毫秒(但返回可迭代/迭代器和將它們轉換爲字符串數組需要總的54ms)