Верхний и нижний регистр
При сравнении без учета регистра более эффективно преобразовывать строку в верхний или нижний регистр? Имеет ли это значение?
В этом SO сообщается, что C# более эффективен с ToUpper, потому что "Microsoft оптимизировала его таким образом". Но я также прочитал этот аргумент, что преобразование ToLower против ToUpper зависит от того, что в ваших строках больше, и что обычно строки содержат больше символов нижнего регистра, что делает ToLower более эффективным.
В частности, я хотел бы знать:
- Есть ли способ оптимизировать ToUpper или ToLower так, чтобы один был быстрее другого?
- Быстрее ли сравнивать строки в верхнем или нижнем регистре без учета регистра и почему?
- Существуют ли какие-либо среды программирования (например, C, C#, Python и т. Д.), Где один случай явно лучше другого и почему?
11 ответов
Преобразование в верхний или нижний регистр для сравнения без учета регистра некорректно из-за "интересных" особенностей некоторых культур, в частности Турции. Вместо этого используйте StringComparer с соответствующими параметрами.
В MSDN есть отличные рекомендации по обработке строк. Вы также можете проверить, что ваш код прошел тест Турции.
РЕДАКТИРОВАТЬ: Обратите внимание на комментарий Нейла о порядковых без учета регистра сравнения. Весь этот мир довольно темный:(
От Microsoft на MSDN:
Рекомендации по использованию строк в.NET Framework
Рекомендации по использованию строк
- Используйте метод String.ToUpperInvariant вместо метода String.ToLowerInvariant при нормализации строк для сравнения.
Зачем? От Microsoft:
Нормализуйте строки в верхний регистр
Существует небольшая группа символов, которые при преобразовании в нижний регистр не могут совершать поездки в оба конца.
Каков пример такого персонажа, который не может совершить путешествие туда и обратно?
- Начало: греческий символ Ро (U+03f1) ϱ
- Прописные буквы: заглавная греческая Ро (U+03a1) Ρ
- Строчная: маленькая греческая Ро (U + 03c1) ρ
ϱ, Ρ, ρ
Вот почему, если вы хотите делать сравнения без учета регистра, вы переводите строки в верхний регистр, а не в нижний регистр.
Согласно MSDN более эффективно передать строки и указать сравнение, чтобы игнорировать регистр:
String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) эквивалентно (но быстрее) вызову
String.Compare(ToUpperInvariant(strA), ToUpperInvariant(strB), StringComparison.Ordinal).
Эти сравнения все еще очень быстрые.
Конечно, если вы сравниваете одну строку снова и снова, это может не сработать.
Основываясь на строках, имеющих тенденцию иметь больше строчных букв, ToLower теоретически должен быть быстрее (много сравнений, но мало назначений).
В C, или при использовании индивидуально доступных элементов каждой строки (таких как строки C или тип строки STL в C++), это на самом деле сравнение байтов - так что сравнение UPPER
ничем не отличается от lower
,
Если вы были подлый и загрузили свои строки в long
вместо массивов, вы получите очень быстрое сравнение всей строки, потому что она может сравнивать 4 байта за раз. Однако время загрузки может сделать его не стоящим.
Почему вам нужно знать, что быстрее? Если вы не выполняете метрическую контрольную нагрузку сравнений, то выполнение одного цикла на пару циклов не имеет значения для скорости выполнения в целом и звучит как преждевременная оптимизация:)
Microsoft оптимизировала ToUpperInvariant()
не ToUpper()
, Разница в том, что инвариант более дружественный к культуре. Если вам нужно сравнивать строки без учета регистра, которые могут различаться в разных культурах, используйте Invariant, в противном случае производительность преобразования не должна иметь значения.
Я не могу сказать, быстрее ли ToUpper() или ToLower(). Я никогда не пробовал, так как у меня никогда не было ситуации, когда производительность имела такое большое значение.
Если вы выполняете сравнение строк в C#, то гораздо быстрее использовать.Equals() вместо преобразования обеих строк в верхний или нижний регистр. Другим большим плюсом использования.Equals() является то, что больше памяти не выделяется для 2 новых строк верхнего / нижнего регистра.
Мне нужны были реальные данные об этом, поэтому я вытащил полный список двухбайтовых символов, которые не работают с
ToLower
или же
ToUpper
. Затем я провел этот тест ниже:
using System;
class Program {
static void Main() {
char[][] pairs = {
new[]{'\u00E5','\u212B'},new[]{'\u00C5','\u212B'},new[]{'\u0399','\u1FBE'},
new[]{'\u03B9','\u1FBE'},new[]{'\u03B2','\u03D0'},new[]{'\u03B5','\u03F5'},
new[]{'\u03B8','\u03D1'},new[]{'\u03B8','\u03F4'},new[]{'\u03D1','\u03F4'},
new[]{'\u03B9','\u1FBE'},new[]{'\u0345','\u03B9'},new[]{'\u0345','\u1FBE'},
new[]{'\u03BA','\u03F0'},new[]{'\u00B5','\u03BC'},new[]{'\u03C0','\u03D6'},
new[]{'\u03C1','\u03F1'},new[]{'\u03C2','\u03C3'},new[]{'\u03C6','\u03D5'},
new[]{'\u03C9','\u2126'},new[]{'\u0392','\u03D0'},new[]{'\u0395','\u03F5'},
new[]{'\u03D1','\u03F4'},new[]{'\u0398','\u03D1'},new[]{'\u0398','\u03F4'},
new[]{'\u0345','\u1FBE'},new[]{'\u0345','\u0399'},new[]{'\u0399','\u1FBE'},
new[]{'\u039A','\u03F0'},new[]{'\u00B5','\u039C'},new[]{'\u03A0','\u03D6'},
new[]{'\u03A1','\u03F1'},new[]{'\u03A3','\u03C2'},new[]{'\u03A6','\u03D5'},
new[]{'\u03A9','\u2126'},new[]{'\u0398','\u03F4'},new[]{'\u03B8','\u03F4'},
new[]{'\u03B8','\u03D1'},new[]{'\u0398','\u03D1'},new[]{'\u0432','\u1C80'},
new[]{'\u0434','\u1C81'},new[]{'\u043E','\u1C82'},new[]{'\u0441','\u1C83'},
new[]{'\u0442','\u1C84'},new[]{'\u0442','\u1C85'},new[]{'\u1C84','\u1C85'},
new[]{'\u044A','\u1C86'},new[]{'\u0412','\u1C80'},new[]{'\u0414','\u1C81'},
new[]{'\u041E','\u1C82'},new[]{'\u0421','\u1C83'},new[]{'\u1C84','\u1C85'},
new[]{'\u0422','\u1C84'},new[]{'\u0422','\u1C85'},new[]{'\u042A','\u1C86'},
new[]{'\u0463','\u1C87'},new[]{'\u0462','\u1C87'}
};
int upper = 0, lower = 0;
foreach (char[] pair in pairs) {
Console.Write(
"U+{0:X4} U+{1:X4} pass: ",
Convert.ToInt32(pair[0]),
Convert.ToInt32(pair[1])
);
if (Char.ToUpper(pair[0]) == Char.ToUpper(pair[1])) {
Console.Write("ToUpper ");
upper++;
} else {
Console.Write(" ");
}
if (Char.ToLower(pair[0]) == Char.ToLower(pair[1])) {
Console.Write("ToLower");
lower++;
}
Console.WriteLine();
}
Console.WriteLine("upper pass: {0}, lower pass: {1}", upper, lower);
}
}
Результат ниже. Обратите внимание, что я также тестировал
Invariant
версии, и результат был точно таким же. Интересно, что одна из пар не справляется с обоими. Но исходя из этого ToUpper - лучший вариант .
U+00E5 U+212B pass: ToLower
U+00C5 U+212B pass: ToLower
U+0399 U+1FBE pass: ToUpper
U+03B9 U+1FBE pass: ToUpper
U+03B2 U+03D0 pass: ToUpper
U+03B5 U+03F5 pass: ToUpper
U+03B8 U+03D1 pass: ToUpper
U+03B8 U+03F4 pass: ToLower
U+03D1 U+03F4 pass:
U+03B9 U+1FBE pass: ToUpper
U+0345 U+03B9 pass: ToUpper
U+0345 U+1FBE pass: ToUpper
U+03BA U+03F0 pass: ToUpper
U+00B5 U+03BC pass: ToUpper
U+03C0 U+03D6 pass: ToUpper
U+03C1 U+03F1 pass: ToUpper
U+03C2 U+03C3 pass: ToUpper
U+03C6 U+03D5 pass: ToUpper
U+03C9 U+2126 pass: ToLower
U+0392 U+03D0 pass: ToUpper
U+0395 U+03F5 pass: ToUpper
U+03D1 U+03F4 pass:
U+0398 U+03D1 pass: ToUpper
U+0398 U+03F4 pass: ToLower
U+0345 U+1FBE pass: ToUpper
U+0345 U+0399 pass: ToUpper
U+0399 U+1FBE pass: ToUpper
U+039A U+03F0 pass: ToUpper
U+00B5 U+039C pass: ToUpper
U+03A0 U+03D6 pass: ToUpper
U+03A1 U+03F1 pass: ToUpper
U+03A3 U+03C2 pass: ToUpper
U+03A6 U+03D5 pass: ToUpper
U+03A9 U+2126 pass: ToLower
U+0398 U+03F4 pass: ToLower
U+03B8 U+03F4 pass: ToLower
U+03B8 U+03D1 pass: ToUpper
U+0398 U+03D1 pass: ToUpper
U+0432 U+1C80 pass: ToUpper
U+0434 U+1C81 pass: ToUpper
U+043E U+1C82 pass: ToUpper
U+0441 U+1C83 pass: ToUpper
U+0442 U+1C84 pass: ToUpper
U+0442 U+1C85 pass: ToUpper
U+1C84 U+1C85 pass: ToUpper
U+044A U+1C86 pass: ToUpper
U+0412 U+1C80 pass: ToUpper
U+0414 U+1C81 pass: ToUpper
U+041E U+1C82 pass: ToUpper
U+0421 U+1C83 pass: ToUpper
U+1C84 U+1C85 pass: ToUpper
U+0422 U+1C84 pass: ToUpper
U+0422 U+1C85 pass: ToUpper
U+042A U+1C86 pass: ToUpper
U+0463 U+1C87 pass: ToUpper
U+0462 U+1C87 pass: ToUpper
upper pass: 46, lower pass: 8
Если вы сделаете это правильно, у вас должно быть небольшое, незначительное преимущество в скорости, если вы конвертируете в нижний регистр, но это, как намекнули многие, зависит от культуры и не наследуется в функции, а в строках, которые вы конвертируете (много строчных букв означает несколько присваиваний памяти) - преобразование в верхний регистр происходит быстрее, если у вас есть строка с большим количеством заглавных букв.
Это действительно не должно иметь никакого значения. С символами ASCII это определенно не имеет значения - это всего лишь несколько сравнений и некоторые изменения в любом направлении. Юникод может быть немного сложнее, поскольку есть некоторые символы, которые странным образом меняют регистр, но на самом деле не должно быть никакой разницы, если ваш текст не заполнен этими специальными символами.
Это зависит. Как указано выше, проста только ASCII, она идентична. В.NET читайте и используйте String.Compare, его правильно для вещей i18n (языковые культуры и юникод). Если вы знаете что-либо о вероятности ввода, используйте более распространенный случай.
Помните, что если вы выполняете многократное сравнение строк, длина - отличный первый дискриминатор.
Если вы имеете дело с чистым ASCII, это не имеет значения. Это просто OR x,32 против AND x,224. Юникод, понятия не имею...