Trim whitespace from the end of a StringBuilder without calling ToString().Trim() and back to a new SB

雨燕双飞 提交于 2019-12-04 15:35:13

问题


What is an efficient way to trim whitespace from the end of a StringBuilder without calling ToString().Trim() and back to a new SB new StringBuilder(sb.ToString().Trim()).


回答1:


The following is an extension method, so you can call it like this:

    sb.TrimEnd();

Also, it returns the SB instance, allowing you to chain other calls (sb.TrimEnd().AppendLine()).

    public static StringBuilder TrimEnd(this StringBuilder sb)
    {
        if (sb == null || sb.Length == 0) return sb;

        int i = sb.Length - 1;
        for (; i >= 0; i--)
            if (!char.IsWhiteSpace(sb[i]))
                break;

        if (i < sb.Length - 1)
            sb.Length = i + 1;

        return sb;
    }

Notes:

1) If Null or Empty, returns.

2) If no Trim is actually needed, we're talking a very quick return time, with probably the most expensive call being the single call to char.IsWhiteSpace. So practically zero expense to call TrimEnd when not needed, as opposed to these ToString().Trim() back to SB routes.

3) Else, the most expensive thing, if trim is needed, is the multiple calls to char.IsWhiteSpace (breaks on first non-whitespace char). Of course, the loop iterates backwards; if all are whitespace you'll end up with a SB.Length of 0.

4) If whitespaces were encountered, the i index is kept outside the loop which allows us to cut the Length appropriately with it. IN StringBuilder, this is incredibly performant, it simply sets an internal length integer (the internal char[] is kept the same internal length).

UPDATE2: see excellent notes by Ryan Emerle as follows, which correct some of my misunderstandings (the internal workings of SB are a little more complicated than I made it out to be):

The StringBuilder is technically a linked list of blocks of char[] so we don't end up in the LOH. Adjusting the length isn't technically as simple as changing the end index because if you move into a different chunk the Capacity must be maintained, so a new chunk may need to be allocated. Nevertheless, you only set the Length property at the end, so this seems like a great solution. Relevant details from Eric Lippert: https://stackoverflow.com/a/6524401/62195

Also, see this nice article discussing the .NET 4.0 new StringBuilder implementation: http://1024strongoxen.blogspot.com/2010/02/net-40-stringbuilder-implementation.html

------- UPDATE --------

Following illustrates what happens when a StringBuilder Length is altered (the only real operation done to the SB here, and that only when needed):

        StringBuilder sb = new StringBuilder("cool  \t \r\n ");

        sb.Capacity.Print(); // 16
        sb.Length.Print();  // 11

        sb.TrimEnd();

        sb.Capacity.Print(); // 16
        sb.Length.Print();  // 4 

You can see the internal array (m_ChunkChars) stays the same size after changing the Length, and in fact, you can see in the debugger it doesn't even overwrite the (in this case whitespace) characters. They are orphaned is all.




回答2:


You can try this:

StringBuilder b = new StringBuilder();
b.Append("some words");
b.Append(" to test   ");

int count = 0;
for (int i = b.Length - 1; i >= 0; i--)
{
    if (b[i] == ' ')
        count++;
    else
        break;
}

b.Remove(b.Length - count, count);
string result = b.ToString();

It will just iterate through the end while there are whitespaces then breaking out of the loop.

Or even like this:

StringBuilder b = new StringBuilder();
b.Append("some words");
b.Append(" to test   ");

do
{
    if(char.IsWhiteSpace(b[b.Length - 1]))
    {
         b.Remove(b.Length - 1,1);
    }
}
while(char.IsWhiteSpace(b[b.Length - 1]));

string get = b.ToString();



回答3:


To do a full trim, it's not performant / advisable to do that on the StringBuilder level, but rather at ToString time, like with this TrimToString implementation:

    public static string TrimToString(this StringBuilder sb)
    {
        if (sb == null) return null;

        sb.TrimEnd(); // handles nulle and is very inexpensive, unlike trimstart

        if (sb.Length > 0 && char.IsWhiteSpace(sb[0])) {
            for (int i = 0; i < sb.Length; i++)
                if (!char.IsWhiteSpace(sb[i]))
                    return sb.ToString(i);
            return ""; // shouldn't reach here, bec TrimEnd should have caught full whitespace strings, but ...
        }

        return sb.ToString();
    }



回答4:


public static class StringBuilderExtensions
{
    public static StringBuilder Trim(this StringBuilder builder)
    {
        if (builder.Length == 0)
            return builder;

        var count = 0;
        for (var i = 0; i < builder.Length; i++)
        {
            if (!char.IsWhiteSpace(builder[i]))
                break;
            count++;
        }

        if (count > 0)
        {
            builder.Remove(0, count);
            count = 0;
        }

        for (var i = builder.Length - 1; i >= 0; i--)
        {
            if (!char.IsWhiteSpace(builder[i]))
                break;
            count++;
        }

        if (count > 0)
            builder.Remove(builder.Length - count, count);

        return builder;
    }
}



回答5:


StringBuilder myString = new StringBuilder("This is Trim test ");

if (myString[myString.Length - 1].ToString() == " ")
{              
    myString = myString.Remove(myString.Length - 1, 1);
}


来源:https://stackoverflow.com/questions/24769701/trim-whitespace-from-the-end-of-a-stringbuilder-without-calling-tostring-trim

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!