2009-06-12 49 views
5

通過this鼓勵,而事實上我有十億串的解析,我想修改我的代碼接受的StringTokenizer代替的String []複製String.split與StringTokenizer的

唯一剩下在我和獲得美味的x2性能提升之間的事實是,當你在做

"dog,,cat".split(",") 
//output: ["dog","","cat"] 

StringTokenizer("dog,,cat") 
// nextToken() = "dog" 
// nextToken() = "cat" 

我怎樣才能達到與StringTokenizer類似的結果?有更快的方法來做到這一點?

回答

12

你真的只用逗號進行標記嗎?如果是這樣,我會寫我自己的標記器 - 它可能最終會比更通用的StringTokenizer更有效,它可以查找多個標記,並且可以使其行爲符合您的需要。對於這樣一個簡單的用例,它可以是一個簡單的實現。

如果它很有用,甚至可以實現Iterable<String>,並通過強類型獲得增強型for循環支持,而不是StringTokenizer提供的Enumeration支持。讓我知道,如果你想幫助編寫這樣一個野獸,那真的不應該太難。

此外,我會嘗試在跳過現有解決方案太遠之前對您的實際數據運行性能測試。你有什麼想法你的執行時間有多少實際上是花費在String.split?我知道你有很多要解析的字符串,但是如果你之後對它們做了任何重要的事情,我希望這比分裂更重要。

+1

+1,我喜歡實施Iterable 的想法! – coobird 2009-06-12 13:13:51

+0

謝謝喬恩,我手工解析(使用大量的indexof),現在它快了x4! – Dani 2009-06-12 13:56:03

2

根據你需要標記的字符串類型,你可以編寫你自己的基於String.indexOf()的分割器。您還可以創建多核解決方案,以進一步提高性能,因爲字符串的標記化是相互獨立的。工作批次 - 每個核心說100個字符串。做其他的String.split()或watever。

-1

如果您的輸入是結構化的,您可以看看JavaCC編譯器。它會生成一個讀取輸入的java類。它應該是這樣的:

TOKEN { <CAT: "cat"> , <DOG:"gog"> } 

input: (cat() | dog())* 


cat: <CAT> 
    { 
    animals.add(new Animal("Cat")); 
    } 

dog: <DOG> 
    { 
    animals.add(new Animal("Dog")); 
    } 
2

而不是StringTokenizer的,你可以從Apache的Commons Lang中,我引用嘗試StrTokenizer類:

這個類可以將字符串分割成許多較小的字符串。它旨在爲StringTokenizer做類似的工作,但它提供了更多的控制和靈活性,包括實現ListIterator接口。

空標記可能被刪除或返回爲空。

這聽起來像你所需要的,我想?

4

注意:做了一些快速的基準測試後,Scanner的測試結果比String.split大約慢了四倍。因此,請勿使用掃描儀。

(我離開後直至記錄的事實,掃描儀在這種情況下,一個壞主意(讀作:不downvote我要提示掃描儀,請...))

假設您正在使用Java 1。5或更高版本,嘗試Scanner,它實現Iterator<String>,因爲它發生:

Scanner sc = new Scanner("dog,,cat"); 
sc.useDelimiter(","); 
while (sc.hasNext()) { 
    System.out.println(sc.next()); 
} 

給出:

dog 

cat 
+2

我相信Scanner在內部使用正則表達式,所以OP可能無法獲得他們正在尋找的性能提升。值得一試雖然,與一個合適的基準:) – 2009-06-12 13:31:26

1

你可以做這樣的事情。這並不完美,但它可能適合你。

public static List<String> find(String test, char c) { 
    List<String> list = new Vector<String>(); 
    start; 
    int i=0; 
    while (i<=test.length()) { 
     int start = i; 
     while (i<test.length() && test.charAt(i)!=c) { 
      i++; 
     } 
     list.add(test.substring(start, i)); 
     i++; 
    } 
    return list; 
} 

如果可能的話,你可以ommit名單的事情,直接做一些事來的字符串:

public static void split(String test, char c) { 
    int i=0; 
    while (i<=test.length()) { 
     int start = i; 
     while (i<test.length() && test.charAt(i)!=c) { 
      i++; 
     } 
     String s = test.substring(start,i); 
     // do something with the string here 
     i++; 
    } 
} 

在我的系統的最後一個方法比StringTokenizer的解決方案更快,但你可能想測試它如何爲你工作。 (當然你可以通過忽略第二個元素來縮短這個方法的時間,當然你可以使用for循環而不是外部的while循環,並且包含最後一個i ++,但是我沒有'做T,在這裏,因爲我認爲不好的風格。

0

嗯,你可以做的是手動遍歷字符串最快的東西,如

List<String> split(String s) { 
     List<String> out= new ArrayList<String>(); 
      int idx = 0; 
      int next = 0; 
     while ((next = s.indexOf(',', idx)) > -1) { 
      out.add(s.substring(idx, next)); 
      idx = next + 1; 
     } 
     if (idx < s.length()) { 
      out.add(s.substring(idx)); 
     } 
       return out; 
    } 

這(非正式的測試)看起來是這樣的兩倍與分割速度一樣快但是,這樣迭代會有點危險,例如它會在轉義的逗號上打破,如果最終需要在某個時候處理它(因爲您的十億個字符串的列表中有3個轉義的逗號)在你允許的時候d因爲你可能最終會失去一些速度優勢。

最終它可能不值得費心。

10

經過修改StringTokenizer班後,我無法找到一種方法來滿足返回["dog", "", "cat"]的要求。

此外,StringTokenizer級僅出於兼容性的原因,並鼓勵使用String.split。從用於StringTokenizer的API規格:

StringTokenizer是傳統類 ,但留作兼容性 原因雖然不鼓勵使用在新代碼 。建議任何尋求此功能的人都使用split方法 或Stringjava.util.regex 包代替。

由於問題是String.split方法的性能差,我們需要找到一個替代方案。

注:我說:「按說表現不佳」,因爲很難確定每個用例將會導致StringTokenizer是優於String.split方法。此外,在很多情況下,除非字符串的標記化確實是通過適當的分析確定的應用程序的瓶頸,否則我覺得如果有的話,它最終會成爲不成熟的優化。我傾向於說在寫入優化之前編寫有意義且易於理解的代碼。

現在,從目前的要求來看,可能滾動我們自己的標記器並不會太困難。

滾動我們自己的令牌機!

以下是我寫的一個簡單的分詞器。我要指出,沒有速度的優化,也沒有錯誤檢查,以防止打算過去字符串的結尾 - 這是一個快速和骯髒的實現:

class MyTokenizer implements Iterable<String>, Iterator<String> { 
    String delim = ","; 
    String s; 
    int curIndex = 0; 
    int nextIndex = 0; 
    boolean nextIsLastToken = false; 

    public MyTokenizer(String s, String delim) { 
    this.s = s; 
    this.delim = delim; 
    } 

    public Iterator<String> iterator() { 
    return this; 
    } 

    public boolean hasNext() { 
    nextIndex = s.indexOf(delim, curIndex); 

    if (nextIsLastToken) 
     return false; 

    if (nextIndex == -1) 
     nextIsLastToken = true; 

    return true; 
    } 

    public String next() { 
    if (nextIndex == -1) 
     nextIndex = s.length(); 

    String token = s.substring(curIndex, nextIndex); 
    curIndex = nextIndex + 1; 

    return token; 
    } 

    public void remove() { 
    throw new UnsupportedOperationException(); 
    } 
} 

MyTokenizer將採取String標記並將String作爲分隔符,並使用String.indexOf方法執行分隔符的搜索。令牌由String.substring方法生成。

我想通過在char[]級別而不是String級別處理字符串可能會有一些性能改進。但是我會把它作爲練習留給讀者。

類也爲了充分利用這是在Java 5中StringTokenizer推出for-each循環結構的實現IterableIteratorEnumerator,並且不支持for-each結構。

它有什麼更快嗎?

爲了搞清楚這是更快了,我寫了一個程序在以下四種方法來比較速度:

  1. 使用StringTokenizer
  2. 使用新的MyTokenizer
  3. 使用String.split
  4. 使用Pattern.compile預編譯的正則表達式。

在四種方法中,字符串"dog,,cat"被分隔爲標記。雖然比較中包含StringTokenizer,但應注意的是,它不會返回["dog", "", "cat]的預期結果。

標記化重複了總共100萬次,給了足夠的時間來注意方法的差異。

用於簡單的基準代碼是下面的:

long st = System.currentTimeMillis(); 
for (int i = 0; i < 1e6; i++) { 
    StringTokenizer t = new StringTokenizer("dog,,cat", ","); 
    while (t.hasMoreTokens()) { 
    t.nextToken(); 
    } 
} 
System.out.println(System.currentTimeMillis() - st); 

st = System.currentTimeMillis(); 
for (int i = 0; i < 1e6; i++) { 
    MyTokenizer mt = new MyTokenizer("dog,,cat", ","); 
    for (String t : mt) { 
    } 
} 
System.out.println(System.currentTimeMillis() - st); 

st = System.currentTimeMillis(); 
for (int i = 0; i < 1e6; i++) { 
    String[] tokens = "dog,,cat".split(","); 
    for (String t : tokens) { 
    } 
} 
System.out.println(System.currentTimeMillis() - st); 

st = System.currentTimeMillis(); 
Pattern p = Pattern.compile(","); 
for (int i = 0; i < 1e6; i++) { 
    String[] tokens = p.split("dog,,cat"); 
    for (String t : tokens) { 
    } 
} 
System.out.println(System.currentTimeMillis() - st); 

結果

試驗是使用Java SE 6運行(建立1.6.0_12-B04),和結果以下內容:

 
        Run 1 Run 2 Run 3 Run 4 Run 5 
        ----- ----- ----- ----- ----- 
StringTokenizer  172  188  187  172  172 
MyTokenizer   234  234  235  234  235 
String.split  1172  1156  1171  1172  1156 
Pattern.compile  906  891  891  907  906 

所以,可以從有限的測試中可以看出只有5次運行期間,StringTokenizer其實ç做歐米出局最快,但MyTokenizer進入第二。然後,String.split是最慢的,並且預編譯的正則表達式比split方法稍快。

與任何小基準一樣,它可能不是真實生活條件的代表,所以結果應該與鹽的穀物(或丘)一起拍攝。

0

我會推薦Google的Guava Splitter
coobird檢驗比較,並得到以下結果:

的StringTokenizer 104
谷歌番石榴分配器142
String.split 446
正則表達式299