Какой наиболее эффективный способ определить, является ли непокрытая строка пустой в С#?
У меня есть строка, которая может содержать пробельные символы, и я хочу проверить, действительно ли она пуста.
Существует несколько способов сделать это:
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))
Я знаю, что это, как правило, явный пример преждевременной оптимизации, но мне любопытно, и есть шанс, что это будет сделано достаточно, чтобы повлиять на производительность.
Итак, какой из них является наиболее эффективным методом?
Есть ли лучшие методы, о которых я не думал?
Изменить: Заметки для посетителей по этому вопросу:
-
Были некоторые удивительно подробные исследования этого вопроса, в частности, Энди и Джона Скита.
-
Если вы наткнулись на вопрос во время поиска чего-то, это стоит того, чтобы читать, по крайней мере, сообщения Энди и Джона полностью.
Похоже, что существует несколько очень эффективных методов, и наиболее эффективный зависит от содержимого строк, с которыми мне нужно иметь дело.
Если я не могу предсказать строки (что я не могу в моем случае), методы Jon IsEmptyOrWhiteSpace
кажутся более быстрыми.
Спасибо всем за ваш вклад. Я собираюсь выбрать Энди, как "правильный", просто потому, что он заслуживает повышения репутации за свои усилия, и у Джона уже есть репутация в 11 миллионов миллиардов.
Ответы
Ответ 1
Изменить: новые тесты:
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 Post for algorithm
ticks: 6597776
ticks: 9988304
ticks: 7855664
ticks: 7826296
ticks: 7885200
ticks: 7872776
7. is (string.IsNullOrEmpty(myString.Trim()) //Cloud 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
}
Ответ 2
(EDIT: см. нижнюю часть столбца для тестов для различных микрооптимизаций метода)
Не обрезайте его - это может создать новую строку, которая вам действительно не нужна. Вместо этого просмотрите строку для любых символов, которые не являются пробелами (для любого определения, которое вы хотите). Например:
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
Ответ 3
Я действительно не знаю, что быстрее; хотя мое чувство кишки говорит номер один. Но здесь другой метод:
if (String.IsNullOrEmpty(myString.Trim()))
Ответ 4
myString.Trim(). Длина == 0 Взято: 421 мс
myString.Trim() == '' взял: 468 мс
if (myString.Trim(). Equals ("")) Взял: 515 мс
if (myString.Trim() == String.Empty) Взял: 484 мс
if (myString.Trim(). Equals (String.Empty)) Взял: 500 мс
if (string.IsNullOrEmpty(myString.Trim())) Взял: 437 мс
В моих тестах он выглядит как myString.Trim(). Length == 0 и, что удивительно, string.IsNullOrEmpty(myString.Trim()) были последовательно самыми быстрыми. Результаты, приведенные выше, являются типичным результатом выполнения 100 000 000 сравнений.
Ответ 5
Проверка длины строки за ноль - самый эффективный способ проверить пустую строку, поэтому я бы сказал номер 1:
if (myString.Trim().Length == 0)
Единственный способ оптимизировать это дальше - избежать обрезки с помощью скомпилированного регулярного выражения (Edit: это на самом деле намного медленнее, чем использование Trim(). Length).
Изменить: предложение использовать длину было получено из руководства FxCop. Я также просто протестировал его: он в 2-3 раза быстрее, чем по сравнению с пустой строкой. Однако оба подхода все еще очень быстрые (мы говорим о наносекундах) - так что вряд ли имеет значение то, что вы используете. Обрезка является гораздо более узким местом в сотни раз медленнее, чем фактическое сравнение в конце.
Ответ 6
String.IsNullOrWhitespace в .NET 4 Beta 2 также воспроизводится в этом пространстве и не нуждается в пользовательской записи
Ответ 7
Так как я только начал, я не могу прокомментировать, так вот.
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
}
Ответ 8
public static bool IsNullOrEmpty(this String str, bool checkTrimmed)
{
var b = String.IsNullOrEmpty(str);
return checkTrimmed ? b && str.Trim().Length == 0 : b;
}