Какой самый эффективный способ определить, является ли не обрезанная строка пустой в C #? - PullRequest
14 голосов
/ 01 мая 2009

У меня есть строка, в которой могут быть пробельные символы, и я хочу проверить, действительно ли она пуста.

Есть несколько способов сделать это:

1  if (myString.Trim().Length == 0)
2  if (myString.Trim() == "")
3  if (myString.Trim().Equals(""))
4  if (myString.Trim() == String.Empty)
5  if (myString.Trim().Equals(String.Empty))

Я знаю, что обычно это явный случай преждевременной оптимизации, но мне любопытно, и есть шанс, что этого будет достаточно, чтобы повлиять на производительность.

Так какой из этих методов наиболее эффективен?

Есть ли лучшие методы, о которых я не думал?


Редактировать: Примечания для посетителей этого вопроса:

  1. По этому вопросу было проведено несколько удивительно подробных исследований, особенно от Энди и Джона Скита.

  2. Если вы наткнулись на вопрос, когда что-то искали, стоит прочитать хотя бы сообщения Энди и Джона в целом.

Кажется, есть несколько очень эффективных методов, и самый эффективный зависит от содержимого строк, с которыми мне нужно иметь дело.

Если я не могу предсказать строки (что я не могу в моем случае), методы Джона IsEmptyOrWhiteSpace, как правило, работают быстрее.

Спасибо всем за ваш вклад. Я выберу ответ Энди как «правильный» просто потому, что он заслуживает повышения репутации за приложенные усилия, а у Джона уже около одиннадцати миллиардов репутации.

Ответы [ 8 ]

19 голосов
/ 01 мая 2009

Редактировать: Новые тесты:

Test orders:
x. Test name
Ticks: xxxxx //Empty String
Ticks: xxxxx //two space
Ticks: xxxxx //single letter
Ticks: xxxxx //single letter with space
Ticks: xxxxx //long string
Ticks: xxxxx //long string  with space

1. if (myString.Trim().Length == 0)
ticks: 4121800
ticks: 7523992
ticks: 17655496
ticks: 29312608
ticks: 17302880
ticks: 38160224

2.  if (myString.Trim() == "")
ticks: 4862312
ticks: 8436560
ticks: 21833776
ticks: 32822200
ticks: 21655224
ticks: 42358016


3.  if (myString.Trim().Equals(""))
ticks: 5358744
ticks: 9336728
ticks: 18807512
ticks: 30340392
ticks: 18598608
ticks: 39978008


4.  if (myString.Trim() == String.Empty)
ticks: 4848368
ticks: 8306312
ticks: 21552736
ticks: 32081168
ticks: 21486048
ticks: 41667608


5.  if (myString.Trim().Equals(String.Empty))
ticks: 5372720
ticks: 9263696
ticks: 18677728
ticks: 29634320
ticks: 18551904
ticks: 40183768


6.  if (IsEmptyOrWhitespace(myString))  //See John Skeet's Post for algorithm
ticks: 6597776
ticks: 9988304
ticks: 7855664
ticks: 7826296
ticks: 7885200
ticks: 7872776

7. is (string.IsNullOrEmpty(myString.Trim())  //Cloud's suggestion
ticks: 4302232
ticks: 10200344
ticks: 18425416
ticks: 29490544
ticks: 17800136
ticks: 38161368

И используемый код:

public void Main()
{

    string res = string.Empty;

    for (int j = 0; j <= 5; j++) {

        string myString = "";

        switch (j) {

            case 0:
                myString = "";
                break;
            case 1:
                myString = "  ";
                break;
            case 2:
                myString = "x";
                break;
            case 3:
                myString = "x ";
                break;
            case 4:
                myString = "this is a long string for testing triming empty things.";
                break;
            case 5:
                myString = "this is a long string for testing triming empty things. ";

                break;
        }

        bool result = false;
        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i <= 100000; i++) {


            result = myString.Trim().Length == 0;
        }
        sw.Stop();


        res += "ticks: " + sw.ElapsedTicks + Environment.NewLine;
    }


    Console.ReadKey();  //break point here to get the results
}
15 голосов
/ 01 мая 2009

(РЕДАКТИРОВАТЬ: см. Внизу поста для тестов по различным микро-оптимизации метода)

Не обрезайте его - это может создать новую строку, которая вам на самом деле не нужна. Вместо этого просмотрите строку для любых символов, которые не являются пробелами (для любого определения, которое вы хотите). Например:

public static bool IsEmptyOrWhitespace(string text)
{
    // Avoid creating iterator for trivial case
    if (text.Length == 0)
    {
        return true;
    }
    foreach (char c in text)
    {
        // Could use Char.IsWhiteSpace(c) instead
        if (c==' ' || c=='\t' || c=='\r' || c=='\n')
        {
            continue;
        }
        return false;
    }
    return true;
}

Вы также можете рассмотреть, что вы хотите, чтобы метод делал, если text равно null.

Возможны дальнейшие микрооптимизации для экспериментов:

  • Является ли foreach быстрее или медленнее, чем использование цикла for, как показано ниже? Обратите внимание, что с помощью цикла for вы можете удалить тест «if (text.Length==0)» при запуске.

    for (int i = 0; i < text.Length; i++)
    {
        char c = text[i];
        // ...
    
  • То же, что и выше, но вызов Length. Обратите внимание, что это не хорошо для обычных массивов, но может быть полезным для строк. Я не проверял это.

    int length = text.Length;
    for (int i = 0; i < length; i++)
    {
        char c = text[i];
    
  • В теле цикла есть ли разница (по скорости) между тем, что у нас есть, и:

    if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
    {
        return false;
    }
    
  • Будет ли переключатель / корпус быстрее?

    switch (c)
    {
        case ' ': case '\r': case '\n': case '\t':
            return false;               
    }
    

Обновление поведения Trim

Я только что выяснил, как Trim может быть столь же эффективным, как этот. Кажется, что Trim создаст новую строку, только если это необходимо. Если он может вернуть this или "", он будет:

using System;

class Test
{
    static void Main()
    {
        CheckTrim(string.Copy(""));
        CheckTrim("  ");
        CheckTrim(" x ");
        CheckTrim("xx");
    }

    static void CheckTrim(string text)
    {
        string trimmed = text.Trim();
        Console.WriteLine ("Text: '{0}'", text);
        Console.WriteLine ("Trimmed ref == text? {0}",
                          object.ReferenceEquals(text, trimmed));
        Console.WriteLine ("Trimmed ref == \"\"? {0}",
                          object.ReferenceEquals("", trimmed));
        Console.WriteLine();
    }
}

Это означает, что очень важно, чтобы любые тесты в этом вопросе использовали комбинацию данных:

  • Пустая строка
  • Пробелы
  • Пробел, окружающий текст
  • Текст без пробелов

Конечно, баланс "реального мира" между этими четырьмя невозможно предсказать ...

Тесты Я провел несколько тестов исходных предложений по сравнению с моими, и мое, кажется, выигрывает во всем, что я в него бросаю, что удивляет меня, учитывая результаты в других ответах. Тем не менее, я также сравнил разницу между foreach, for с использованием text.Length, for с использованием text.Length один раз и затем с обратным порядком итерации и for с длиной подъема.

По сути, цикл for очень немного быстрее, но подъем проверки длины делает его медленнее, чем foreach. Изменение направления петли for тоже немного медленнее, чем foreach. Я сильно подозреваю, что JIT делает здесь интересные вещи с точки зрения удаления повторяющихся проверок границ и т. Д.

Код: (см. мою запись в блоге по бенчмаркингу для фреймворка, против которого она написана)

using System;
using BenchmarkHelper;

public class TrimStrings
{
    static void Main()
    {
        Test("");
        Test(" ");
        Test(" x ");
        Test("x");
        Test(new string('x', 1000));
        Test(" " + new string('x', 1000) + " ");
        Test(new string(' ', 1000));
    }

    static void Test(string text)
    {
        bool expectedResult = text.Trim().Length == 0;
        string title = string.Format("Length={0}, result={1}", text.Length, 
                                     expectedResult);

        var results = TestSuite.Create(title, text, expectedResult)
/*            .Add(x => x.Trim().Length == 0, "Trim().Length == 0")
            .Add(x => x.Trim() == "", "Trim() == \"\"")
            .Add(x => x.Trim().Equals(""), "Trim().Equals(\"\")")
            .Add(x => x.Trim() == string.Empty, "Trim() == string.Empty")
            .Add(x => x.Trim().Equals(string.Empty), "Trim().Equals(string.Empty)")
*/
            .Add(OriginalIsEmptyOrWhitespace)
            .Add(IsEmptyOrWhitespaceForLoop)
            .Add(IsEmptyOrWhitespaceForLoopReversed)
            .Add(IsEmptyOrWhitespaceForLoopHoistedLength)
            .RunTests()                          
            .ScaleByBest(ScalingMode.VaryDuration);

        results.Display(ResultColumns.NameAndDuration | ResultColumns.Score,
                        results.FindBest());
    }

    public static bool OriginalIsEmptyOrWhitespace(string text)
    {
        if (text.Length == 0)
        {
            return true;
        }
        foreach (char c in text)
        {
            if (c==' ' || c=='\t' || c=='\r' || c=='\n')
            {
                continue;
            }
            return false;
        }
        return true;
    }

    public static bool IsEmptyOrWhitespaceForLoop(string text)
    {
        for (int i=0; i < text.Length; i++)
        {
            char c = text[i];
            if (c==' ' || c=='\t' || c=='\r' || c=='\n')
            {
                continue;
            }
            return false;
        }
        return true;
    }

    public static bool IsEmptyOrWhitespaceForLoopReversed(string text)
    {
        for (int i=text.Length-1; i >= 0; i--)
        {
            char c = text[i];
            if (c==' ' || c=='\t' || c=='\r' || c=='\n')
            {
                continue;
            }
            return false;
        }
        return true;
    }

    public static bool IsEmptyOrWhitespaceForLoopHoistedLength(string text)
    {
        int length = text.Length;
        for (int i=0; i < length; i++)
        {
            char c = text[i];
            if (c==' ' || c=='\t' || c=='\r' || c=='\n')
            {
                continue;
            }
            return false;
        }
        return true;
    }
}

Результаты:

============ Length=0, result=True ============
OriginalIsEmptyOrWhitespace             30.012 1.00
IsEmptyOrWhitespaceForLoop              30.802 1.03
IsEmptyOrWhitespaceForLoopReversed      32.944 1.10
IsEmptyOrWhitespaceForLoopHoistedLength 35.113 1.17

============ Length=1, result=True ============
OriginalIsEmptyOrWhitespace             31.150 1.04
IsEmptyOrWhitespaceForLoop              30.051 1.00
IsEmptyOrWhitespaceForLoopReversed      31.602 1.05
IsEmptyOrWhitespaceForLoopHoistedLength 33.383 1.11

============ Length=3, result=False ============
OriginalIsEmptyOrWhitespace             30.221 1.00
IsEmptyOrWhitespaceForLoop              30.131 1.00
IsEmptyOrWhitespaceForLoopReversed      34.502 1.15
IsEmptyOrWhitespaceForLoopHoistedLength 35.690 1.18

============ Length=1, result=False ============
OriginalIsEmptyOrWhitespace             31.626 1.05
IsEmptyOrWhitespaceForLoop              30.005 1.00
IsEmptyOrWhitespaceForLoopReversed      32.383 1.08
IsEmptyOrWhitespaceForLoopHoistedLength 33.666 1.12

============ Length=1000, result=False ============
OriginalIsEmptyOrWhitespace             30.177 1.00
IsEmptyOrWhitespaceForLoop              33.207 1.10
IsEmptyOrWhitespaceForLoopReversed      30.867 1.02
IsEmptyOrWhitespaceForLoopHoistedLength 31.837 1.06

============ Length=1002, result=False ============
OriginalIsEmptyOrWhitespace             30.217 1.01
IsEmptyOrWhitespaceForLoop              30.026 1.00
IsEmptyOrWhitespaceForLoopReversed      34.162 1.14
IsEmptyOrWhitespaceForLoopHoistedLength 34.860 1.16

============ Length=1000, result=True ============
OriginalIsEmptyOrWhitespace             30.303 1.01
IsEmptyOrWhitespaceForLoop              30.018 1.00
IsEmptyOrWhitespaceForLoopReversed      35.475 1.18
IsEmptyOrWhitespaceForLoopHoistedLength 40.927 1.36
4 голосов
/ 01 мая 2009

myString.Trim (). Длина == 0 Взята: 421 мс

myString.Trim () == '' заняло: 468 мс

if (myString.Trim (). Equals ("")) Взял: 515 мс

if (myString.Trim () == String.Empty) Взял: 484 мс

if (myString.Trim (). Equals (String.Empty)) Took: 500 мс

if (string.IsNullOrEmpty (myString.Trim ())) Took: 437 мс

В моих тестах это выглядит как myString.Trim (). Length == 0 и, что удивительно, string.IsNullOrEmpty (myString.Trim ()) всегда были самыми быстрыми. Приведенные выше результаты являются типичным результатом выполнения 10 000 000 сравнений.

4 голосов
/ 01 мая 2009

Я действительно не знаю, что быстрее; хотя мое внутреннее чувство говорит номер один. Но вот еще один метод:

if (String.IsNullOrEmpty(myString.Trim()))
3 голосов
/ 07 ноября 2009

String.IsNullOrWhitespace в .NET 4 Beta 2 также воспроизводится в этом пространстве и не требует специальной записи

3 голосов
/ 01 мая 2009

Проверка длины строки на нулевое значение является наиболее эффективным способом проверки пустой строки, поэтому я бы сказал, что число 1:

if (myString.Trim().Length == 0)

Единственный способ дальнейшей оптимизации может заключаться в том, чтобы избежать обрезки с помощью скомпилированного регулярного выражения (Edit: это на самом деле намного медленнее, чем с помощью Trim (). Length).

Edit: предложение использовать Length пришло из директивы FxCop. Я также только что проверил это: это в 2-3 раза быстрее, чем по сравнению с пустой строкой. Однако оба подхода все еще чрезвычайно быстры (мы говорим о наносекундах) - поэтому вряд ли имеет значение, какой вы используете. Тримминг является гораздо более узким местом, он в сотни раз медленнее, чем фактическое сравнение в конце.

1 голос
/ 01 мая 2009

Поскольку я только начал, я не могу комментировать, так что вот оно.

if (String.IsNullOrEmpty(myString.Trim()))

Trim() вызов завершится неудачно, если myString имеет значение null, поскольку вы не можете вызывать методы в объекте с нулевым значением ( NullReferenceException ).

Таким образом, правильный синтаксис будет выглядеть примерно так:

if (!String.IsNullOrEmpty(myString))
{
    string trimmedString = myString.Trim();
    //do the rest of you code
}
else
{
    //string is null or empty, don't bother processing it
}
0 голосов
/ 27 июля 2009
public static bool IsNullOrEmpty(this String str, bool checkTrimmed)
{
  var b = String.IsNullOrEmpty(str);
  return checkTrimmed ? b && str.Trim().Length == 0 : b;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...