Верхній та нижній регістр


85

При порівнянні, що не враховує регістр, чи ефективніше перетворити рядок у верхній чи нижній регістр? Це взагалі має значення?

У цій публікації SO висувається припущення, що C # є більш ефективним у роботі з ToUpper, оскільки "Microsoft оптимізувала його таким чином". Але я також прочитав цей аргумент, що перетворення ToLower проти ToUpper залежить від того, що ваші рядки містять більше, і що, як правило, рядки містять більше символів нижнього регістру, що робить ToLower більш ефективним.

Зокрема, я хотів би знати:

  • Чи є спосіб оптимізувати ToUpper або ToLower таким чином, що один швидший за інший?
  • Чи швидше зробити нечутливе до регістру порівняння між рядками верхнього чи нижнього регістру, і чому?
  • Чи існують середовища програмування (наприклад, C, C #, Python, будь-які), де один випадок явно кращий за інший, і чому?

Відповіді:


90

Перетворення на верхній або нижній регістр для порівняння, що не враховує регістр, є неправильним через "цікаві" особливості деяких культур, зокрема Туреччини. Натомість використовуйте StringComparer із відповідними параметрами.

MSDN має кілька чудових вказівок щодо обробки рядків. Можливо, ви також захочете перевірити, чи ваш код проходить тест Туреччини .

EDIT: Зверніть увагу на коментар Ніла щодо порядкових порівнянь, що не враховують регістр. Вся ця сфера досить мутна :(


15
Так, StringComparer чудовий, але на запитання відповіді не було ... У ситуаціях, коли ви не можете використовувати StringComparer, наприклад, оператор swtich проти рядка; чи слід мені ToUpper або ToLower у комутаторі?
joshperry

7
Використовуйте StringComparer та "if" / "else" замість використання ToUpper або ToLower.
Джон Скіт,

5
Джон, я знаю, що переводити в нижній регістр неправильно, але я не чув, що перехід у верхній регістр є неправильним. Чи можете ви запропонувати приклад чи посилання? У статті MSDN, до якої ви зв’язали, йдеться так: "Порівняння, зроблені за допомогою OrdinalIgnoreCase, є поведінковою композицією з двох викликів: виклику ToUpperInvariant для обох рядкових аргументів і порівняння за порядковим номером." У розділі під назвою "Звичайні рядкові операції" це повторюється в коді.
Ніл,

2
@Neil: Цікаво, я цього не бачив. Для порядкового порівняння, що не враховує регістр, я думаю, це досить справедливо. Зрештою, потрібно щось вибрати . Для порівняння, що не враховує регістр, я думаю, що все одно знайдеться місце для якоїсь дивної поведінки. Вкаже на ваш коментар у відповіді ...
Джон Скіт,

4
@Triynko: Я думаю, важливо зосередитись насамперед на правильності, з тим, що швидке отримання неправильної відповіді, як правило, не є кращим (а часом і гіршим), ніж отримання повільної неправильної відповіді.
Джон Скіт,

25

Від Microsoft на MSDN:

Найкращі практики використання рядків у .NET Framework

Рекомендації щодо використання рядків

Чому? Від Microsoft :

Нормалізуйте рядки у верхній регістр

Існує невелика група символів, які при перетворенні в малу літеру не можуть здійснити зворотний шлях.

Який приклад такого персонажа, який не може здійснити поїздку в обидва кінці?

  • Початок : грецький символ Ро (U + 03f1) ϱ
  • Великі літери : столиця грецька Rho (U + 03a1) Ρ
  • Малі регістри: мала грецька ро (U + 03c1) ρ

ϱ, Ρ , ρ

.NET Скрипка

Original: ϱ
ToUpper: Ρ
ToLower: ρ

Ось чому, якщо ви хочете робити порівняння, що не враховують регістр, ви перетворюєте рядки у великі, а не в маленькі регістри.

Тож якщо вам доводиться вибирати одну, вибирайте великі літери .


і в чому причина?
bjan

@bjan Причина в тому, що погано це не робити.
Ian Boyd

1
Яка група персонажів? Що взагалі означає подорож у обидва кінці?
johv

1
@johv З посилання: "Здійснити зворотній рейс означає перетворити символи з однієї мови в іншу, яка по-різному представляє дані символів, а потім точно отримати вихідні символи з перетворених символів." Яка група персонажів? Не знаю, але я збираюся вгадати малу iтурецьку мову, коли це стане İ, а не ту I, до якої ти звик. Крім того, ми звикли ставити великі Iрегістри i, але в Туреччині це стає ı.
Ian Boyd

3
Назад до відповіді на вихідне запитання: Існують мови, які знають більше одного варіанту нижнього регістру для одного варіанту верхнього регістру. Якщо ви не знаєте правил, коли використовувати яке подання (інший приклад грецькою мовою: мала буква сигма, ви використовуєте σ на початку слова або в середині, ς на кінці слів (див. En.wikipedia.org/wiki/Sigma ), ви не можете надійно перетворити назад на нижній регістр.
Аконкагуа,

19

Згідно з MSDN , ефективніше передати рядки та сказати порівняння, щоб ігнорувати регістр:

String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) еквівалентно ( але швидше ніж ) виклику

String.Compare (ToUpperInvariant (strA), ToUpperInvariant (strB), StringComparison.Ordinal).

Ці порівняння все ще дуже швидкі.

Звичайно, якщо ви порівнюєте один рядок знову і знову, це може не мати місця.


12

На основі рядків, що мають тенденцію до більшої кількості нижчих регістрів, ToLower теоретично повинен бути швидшим (багато порівнянь, але мало призначень).

У C, або при використанні індивідуально доступних елементів кожного рядка (наприклад, рядків C або типу рядка STL в C ++), це насправді байтове порівняння - тому порівняння UPPERнічим не відрізняється від lower.

Якби ви були підступним і longнатомість завантажували свої рядки в масиви, ви отримали б дуже швидке порівняння цілого рядка, оскільки він міг порівнювати 4 байти одночасно. Однак час завантаження може зробити це не вартом.

Чому потрібно знати, що швидше? Якщо ви не робите метричного порівняльного порівняння, той, який працює на пару циклів швидше, не має значення для швидкості загального виконання і звучить як передчасна оптимізація :)


11
Щоб відповісти на питання, чому мені потрібно знати, що швидше: мені не потрібно знати, я просто хочу знати. :) Це просто випадок, коли хтось висуває претензію (наприклад, „порівняння рядків у верхньому регістрі швидше!“) І хоче знати, чи це справді так і / або чому вони зробили це твердження.
Параппа,

1
це має сенс - мені теж навіки цікаво на такі речі :)
Уоррен

За допомогою рядків C для перетворення sта tмасивів довжини таких, щоб рядки були рівними, якщо масиви рівні, вам доведеться проходити вниз s і t, поки не знайдете закінчувальний '\0'символ (або ви можете порівняти сміття після кінця рядків, що може бути незаконним доступом до пам'яті, який викликає невизначену поведінку). Але чому тоді просто не робити порівняння, проходячи по героях по одному? За допомогою рядків C ++ ви, ймовірно, можете отримати довжину і .c_str(), відтворити на a long *та порівняти префікс length .size() - .size()%(sizeof long). Мені це здається трохи рибним, Тхо.
Jonas Kölker

6

Microsoft оптимізувала ToUpperInvariant(), ні ToUpper(). Різниця полягає в тому, що інваріант є більш сприятливим для культури. Якщо вам потрібно зробити порівняння без урахування регістру рядків, які можуть відрізнятися за культурою, використовуйте Invariant, інакше ефективність перетворення інваріантів не повинна мати значення.

Не можу сказати, швидше ToUpper () чи ToLower (). Я ніколи не пробував, оскільки ніколи не мав ситуації, коли продуктивність так сильно мала значення.


якщо Microsoft оптимізувала код для порівняння великих літер, це тому, що код ASCII для великих літер лише дві цифри 65 - 90, тоді як код ASCII Малі літери 97 -122, який містить 3 цифри (потрібна додаткова обробка)?
Medo Medo

3
@Medo Я не пам'ятаю точних причин оптимізації, але 2 проти 3 цифр майже напевно не причина, оскільки всі літери зберігаються як двійкові числа, тому десяткові цифри насправді не мають значення залежно від способу їх зберігання.
Ден Герберт

4

Якщо ви робите порівняння рядків у C #, це значно швидше використовувати .Equals () замість перетворення обох рядків у верхній чи нижній регістр. Ще одним великим плюсом використання .Equals () є те, що для 2 нових рядків верхнього / нижнього регістру не виділяється більше пам'яті.


4
І як бонус, якщо ви виберете правильні варіанти, це насправді дасть вам правильні результати :)
Джон Скіт,

1

Це справді ніколи не повинно мати значення. Для символів ASCII це однозначно не має значення - це лише кілька порівнянь і трохи перекидання для будь-якого напрямку. Юнікод може бути дещо складнішим, оскільки є деякі символи, які змінюють регістр дивними способами, але насправді не повинно бути ніякої різниці, якщо ваш текст не заповнений цими спеціальними символами.


1

Роблячи це правильно, має бути невелика, незначна перевага в швидкості, якщо ви перетворюєте на нижній регістр, але це, як вже натякали багато, залежить від культури і не успадковується у функції, а в рядках, які ви перетворюєте (багато малих літер означає незначне призначення пам’яті) - перетворення у верхній регістр відбувається швидше, якщо у вас є рядок з великою кількістю великих літер.


0

Це залежить. Як зазначено вище, звичайний ASCII, його ідентичний. У .NET читайте про String та використовуйте його. Порівняйте його правильний для матеріалів i18n (культури мов та Unicode). Якщо ви щось знаєте про ймовірність введення, скористайтеся більш поширеним випадком.

Пам'ятайте, якщо ви виконуєте кілька рядків, порівняння довжини є прекрасним першим розпізнавачем.


-2

Якщо ви маєте справу з чистим ASCII, це не має значення. Це просто АБО x, 32 проти AND x, 224. Юнікод, я поняття не маю ...


4
Це абсолютно неправильно - АБО з 32 творами працює лише для AZ та символів 64-127; це псує всі інші символи. AND'а з 32 ще більше неправильний - результат завжди буде 0 (нуль) або 32 (пробіл).
Адам Розенфілд,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.