Обрезать пробелы из конца StringBuilder, не вызывая ToString(). Trim() и обратно к новому SB

Что такое эффективный способ обрезать пробелы с конца StringBuilder без вызова ToString(). Trim() и обратно к новому SB new StringBuilder(sb.ToString().Trim()).

Ответы

Ответ 1

Ниже приведен метод расширения, поэтому его можно вызвать так:

    sb.TrimEnd();

Кроме того, он возвращает экземпляр SB, позволяя вам sb.TrimEnd().AppendLine() другие вызовы (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;
    }

Заметки:

1) Если ноль или пусто, возвращает.

2) Если на самом деле нет необходимости в Trim, мы говорим об очень быстром времени возврата, при этом, вероятно, самым дорогим вызовом является одиночный вызов char.IsWhiteSpace. Таким образом, практически нет затрат на вызов TrimEnd, когда он не нужен, в отличие от этих ToString(). Trim() обратно на маршруты SB.

3) Иначе, самой дорогой вещью, если необходима подстройка, являются множественные вызовы char.IsWhiteSpace (разрывы на первом непробельном символе). Конечно, цикл повторяется в обратном направлении; если все пробелы, вы получите SB.Length 0.

4) Если были обнаружены пробелы, индекс я сохраняется вне цикла, что позволяет нам соответствующим образом сократить длину. В StringBuilder это невероятно эффективно, оно просто устанавливает целое число внутренней длины (внутренний символ [] остается той же внутренней длины).

ОБНОВЛЕНИЕ 2: посмотрите отличные заметки Райана Эмерла следующим образом, которые исправляют некоторые из моих недоразумений (внутренняя работа SB немного сложнее, чем я предполагал):

StringBuilder - это технически связанный список блоков char [], поэтому мы не попадаем в LOH. Регулировка длины технически не так проста, как изменение конечного индекса, потому что если вы переходите в другой блок, необходимо сохранить Capacity, поэтому может потребоваться выделить новый блок. Тем не менее, вы устанавливаете только свойство Length в конце, так что это кажется отличным решением. Соответствующие подробности от Эрика Липперта: fooobar.com/questions/163664/...

Также см. Эту хорошую статью, в которой обсуждается новая реализация StringBuilder.NET 4.0: http://1024strongoxen.blogspot.com/2010/02/net-40-stringbuilder-implementation.html

------- ОБНОВИТЬ --------

Ниже показано, что происходит, когда длина StringBuilder изменяется (единственная реальная операция, выполняемая здесь для SB, и это только при необходимости):

        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 

Вы можете видеть, что внутренний массив (m_ChunkChars) остается неизменным по размеру после изменения длины, и фактически вы можете видеть в отладчике, что он даже не перезаписывает (в данном случае пробельные символы). Они осиротели, это все.

Ответ 2

Вы можете попробовать следующее:

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();

Он будет просто проходить через конец, тогда как есть пробелы, которые выходят из цикла.

Или даже так:

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

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;
    }
}

Ответ 4

Для полной обрезки делать это не целесообразно/не рекомендуется на уровне StringBuilder, а скорее во время ToString, как в этой реализации TrimToString:

    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();
    }

Ответ 5

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

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