检查StringBuilder
是否以特定字符串结尾的最佳(最短和最快)方法是什么?
如果我只想检查一个字符,那不是问题sb[sb.Length-1] == 'c'
,而是如何检查它是否以更长的字符串结尾?
我可以考虑从"some string".Length
循环并逐个读取字符,但也许存在更简单的东西? :)
最后我希望有这样的扩展方法:
StringBuilder sb = new StringBuilder("Hello world");
bool hasString = sb.EndsWith("world");
答案 0 :(得分:23)
为避免生成完整字符串的性能开销,可以使用占用索引范围的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);
}
编辑 2 :正如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
。
答案 1 :(得分:4)
在msdn上,您可以在how to search text in the StringBuilder object上找到相关主题。您可以使用的两个选项是:
因为第一个选项是不可能的。你必须去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;
}
}
答案 2 :(得分:2)
<强> TL; DR 强>
如果您的目标是在StringBuilder
对象中获取一部分或全部String
的内容,则应使用其ToString
函数。但是如果你还没有完成字符串的创建,那么最好将StringBuilder
视为一个字符数组并以这种方式操作,而不是创建一堆你不需要的字符串。
字符数组上的字符串操作可能因本地化或编码而变得复杂,因为字符串可以以多种方式编码(例如UTF8或Unicode),但其字符(System.Char
)应为16 -bit UTF16值。
我编写了以下方法,如果字符串中存在StringBuilder
,则返回字符串的索引,否则返回-1。您可以使用此方法创建其他常见的String
方法,例如Contains
,StartsWith
和EndsWith
。此方法优于其他方法,因为它应该正确处理本地化和套管,并且不会强迫您在ToString
上调用StringBuilder
。如果指定应该忽略大小写,它会创建一个垃圾值,并且您可以通过使用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
并且你想要结束循环或者只是做一些不同的事情,如果最后几个字符是你期望的一些哨兵值。
答案 3 :(得分:0)
我正在给你你所要求的(有你所说的限制)但不是最好的方法。类似的东西:
StringBuilder sb = new StringBuilder(“Hello world”); bool hasString = sb.Remove(1,sb.Length - “world”.Length)==“world”;
答案 4 :(得分:0)
private static bool EndsWith(this StringBuilder builder, string value) {
return builder.GetLast( value.Length ).SequenceEqual( value );
}
private static IEnumerable<char> GetLast(this StringBuilder builder, int count) {
count = Math.Min( count, builder.Length );
return Enumerable.Range( builder.Length - count, count ).Select( i => builder[ i ] );
}