2013-07-10 114 views
11

什麼是檢查StringBuilder以特定字符串結尾的最佳(最短和最快)方式?.NET StringBuilder - 檢查是否以字符串結尾

如果我想檢查一個字符,這不是一個問題sb[sb.Length-1] == 'c',但如何檢查它是否以更長的字符串結束?

我可以考慮從"some string".Length循環並逐個閱讀字符,但也許存在一些更簡單的東西? :)

最後我希望有擴展方法是這樣的:

StringBuilder sb = new StringBuilder("Hello world"); 
bool hasString = sb.EndsWith("world"); 
+0

反轉字符串並做一個'StartsWith(「world」)'也許? – PoweredByOrange

+0

爲什麼你害怕'ToString'?這就是你如何使用StringBuilders – banging

+0

@PoweredByOrange Hm ... StringBuilder沒有StartsWith方法,並且相反,它將會更加耗費性能,從最後開始檢查,char by char。 –

回答

21

爲了避免產生滿弦的性能開銷,可以使用ToString(int,int)重載需要索引範圍。

public static bool EndsWith(this StringBuilder sb, string test) 
{ 
    if (sb.Length < test.Length) 
     return false; 

    string end = sb.ToString(sb.Length - test.Length, test.Length); 
    return end.Equals(test); 
} 

編輯:它可能是可取的,以限定一過載,需要一個StringComparison參數:

public static bool EndsWith(this StringBuilder sb, string test) 
{ 
    return EndsWith(sb, test, StringComparison.CurrentCulture); 
} 

public static bool EndsWith(this StringBuilder sb, string test, 
    StringComparison comparison) 
{ 
    if (sb.Length < test.Length) 
     return false; 

    string end = sb.ToString(sb.Length - test.Length, test.Length); 
    return end.Equals(test, comparison); 
} 

編輯:由於在評論中指出由Tim S,我的答案(以及其他假設基於字符的平等)的答案存在缺陷,這會影響某些Unicode比較。 Unicode不要求兩個(子)字符串具有相同的字符序列。例如,應將預編碼字符é視爲等於字符e,後跟合併標記U+0301

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); 

string s = "We met at the cafe\u0301"; 
Console.WriteLine(s.EndsWith("café")); // True 

StringBuilder sb = new StringBuilder(s); 
Console.WriteLine(sb.EndsWith("café")); // False 

如果要正確處理這種情況下,它可能是最簡單的只是調用StringBuilder.ToString(),然後使用內置String.EndsWith

+0

哎呀,我的犯規......我錯過了ToString有超負荷的開始和結束索引!謝謝,這就是答案! :) –

+1

不錯的選擇有StringComparer,但編譯器不會放過線end.Equals(測試,比較);他希望StringComparison而不是比較:) –

+2

在某些文化比較中,這不會工作正確。例如。來自http://msdn.microsoft.com/en-us/library/t9h2fbth.aspx的示例,使用StringBuilder都返回'False'。請參閱http://ideone.com/mVHhWR –

0

我給你你要求的東西(限制你的狀態),但不是最好的辦法。例如:

StringBuilder sb = new StringBuilder(「Hello world」); bool hasString = sb.Remove(1,sb.Length - 「world」.Length)==「world」;

+0

您的解決方案存在兩個問題:1)我不想從最初的StringBuilder中刪除任何內容。 2)刪除返回StringBuilder,因此它不能被用作sb ==「string」; –

4

在msdn上,您可以找到有關how to search text in the StringBuilder object的主題。可用的兩個選項是:

  1. 調用ToString並搜索返回的String對象。
  2. 使用Chars屬性可以順序搜索一系列字符。

由於第一個選項是不可能的。你必須去Chars屬性。

public static class StringBuilderExtensions 
{ 
    public static bool EndsWith(this StringBuilder sb, string text) 
    { 
     if (sb.Length < text.Length) 
      return false; 

     var sbLength = sb.Length; 
     var textLength = text.Length; 
     for (int i = 1; i <= textLength; i++) 
     { 
      if (text[textLength - i] != sb[sbLength - i]) 
       return false; 
     } 
     return true; 
    } 
} 
+0

是的,這是很好的解決方案...當我有一些時間時,我會檢查與ToString和For循環的性能差異。無論如何,我認爲ToString(int,int)在內部使用相同的循環,所以性能大致相同......但它只是假設:) –

+0

讓我們知道您的基準測試結果 – Hemario

+0

它有點冗長,但我喜歡該方法不會產生任何垃圾。 – 2013-07-10 20:52:06

2

TL; DR

如果你的目標是獲得一個String對象的一塊或全部的StringBuilder的內容,你應該使用其ToString功能。但是如果你還沒有完成字符串的創建,最好將StringBuilder作爲一個字符數組來操作,而不是創建一堆你不需要的字符串。

字符數組上的字符串操作可能因本地化或編碼而變得複雜,因爲字符串可以用多種方式進行編碼(例如UTF8或Unicode),但其字符(System.Char)應該是16位的UTF16值。

我寫了下面的方法,如果它存在於StringBuilder中,則返回字符串的索引,否則返回-1。您可以使用它來創建其他常見的String方法,如ContainsStartsWithEndsWith。這種方法比其他方法更好,因爲它應該正確處理定位和套管,並且不會強制您在StringBuilder上調用ToString。如果您指定應該忽略大小寫,它會創建一個垃圾值,您可以通過使用Char.ToLower來解決此問題,以最大限度地節省內存,而不是像下面的函數那樣預先計算字符串的小寫字母。 編輯:此外,如果您使用UTF32編碼的字符串,則必須一次比較兩個字符,而不是一個字符。

除非你打算循環,使用大字符串,並進行操作或格式化,否則你可能更適合使用ToString

public static int IndexOf(this StringBuilder stringBuilder, string str, int startIndex = 0, int? count = null, CultureInfo culture = null, bool ignoreCase = false) 
{ 
    if (stringBuilder == null) 
     throw new ArgumentNullException("stringBuilder"); 

    // No string to find. 
    if (str == null) 
     throw new ArgumentNullException("str"); 
    if (str.Length == 0) 
     return -1; 

    // Make sure the start index is valid. 
    if (startIndex < 0 && startIndex < stringBuilder.Length) 
     throw new ArgumentOutOfRangeException("startIndex", startIndex, "The index must refer to a character within the string."); 

    // Now that we've validated the parameters, let's figure out how many characters there are to search. 
    var maxPositions = stringBuilder.Length - str.Length - startIndex; 
    if (maxPositions <= 0) return -1; 

    // If a count argument was supplied, make sure it's within range. 
    if (count.HasValue && (count <= 0 || count > maxPositions)) 
     throw new ArgumentOutOfRangeException("count"); 

    // Ensure that "count" has a value. 
    maxPositions = count ?? maxPositions; 
    if (count <= 0) return -1; 

    // If no culture is specified, use the current culture. This is how the string functions behave but 
    // in the case that we're working with a StringBuilder, we probably should default to Ordinal. 
    culture = culture ?? CultureInfo.CurrentCulture; 

    // If we're ignoring case, we need all the characters to be in culture-specific 
    // lower case for when we compare to the StringBuilder. 
    if (ignoreCase) str = str.ToLower(culture); 

    // Where the actual work gets done. Iterate through the string one character at a time. 
    for (int y = 0, x = startIndex, endIndex = startIndex + maxPositions; x <= endIndex; x++, y = 0) 
    { 
     // y is set to 0 at the beginning of the loop, and it is increased when we match the characters 
     // with the string we're searching for. 
     while (y < str.Length && str[y] == (ignoreCase ? Char.ToLower(str[x + y]) : str[x + y])) 
      y++; 

     // The while loop will stop early if the characters don't match. If it didn't stop 
     // early, that means we found a match, so we return the index of where we found the 
     // match. 
     if (y == str.Length) 
      return x; 
    } 

    // No matches. 
    return -1; 
} 

主要的原因一個一般採用StringBuilder對象,而不是連接字符串是因爲內存的開銷,你承擔,因爲字符串是不可變的。當你在不使用StringBuilder的情況下進行過多的字符串處理時,性能會受到影響,這通常是收集您創建的所有垃圾字符串的結果。

藉此例如:

string firstString = "1st", 
     secondString = "2nd", 
     thirdString = "3rd", 
     fourthString = "4th"; 
string all = firstString; 
all += " & " + secondString; 
all += " &" + thirdString; 
all += "& " + fourthString + "."; 

如果你運行這一點,在內存分析器打開它,你會發現一組字符串是這個樣子的:

 
"1st", "2nd", "3rd", "4th", 
" & ", " & 2nd", "1st & 2nd" 
" &", "&3rd", "1st & 2nd &3rd" 
"& ", "& 4th", "& 4th." 
"1st & 2nd &3rd& 4th." 

這是我們在該範圍內創建的十四個對象,但是如果您沒有意識到每次添加運算符都會創建一個全新的字符串,那麼您每次可能認爲只有五個對象。那麼其他九個琴絃會發生什麼?他們在記憶中疲於奔命,直到垃圾收集者決定接收它們。

所以現在我的觀點是:如果你想找到一些關於StringBuilder對象的東西,並且你不想打電話給ToString(),這可能意味着你還沒有完成構建該字符串。如果您試圖查明構建器是否以「Foo」結尾,則撥打sb.ToString(sb.Length - 1, 3) == "Foo"會很浪費,因爲您正在創建另一個字符串對象,在您撥打電話的那一刻變成孤兒和過時。

我的猜測是,您正在運行一個循環,將文本聚合到您的StringBuilder中,並且您希望結束循環,或者如果最後幾個字符是您期望的某個標記值,則只需執行一些不同的操作。

相關問題