Wielkie a małe litery


85

Czy w przypadku porównań bez rozróżniania wielkości liter bardziej wydajne jest przekonwertowanie ciągu znaków na wielkie czy małe litery? Czy to w ogóle ma znaczenie?

W tym poście SO sugeruje się, że język C # jest bardziej wydajny z ToUpper, ponieważ „Microsoft zoptymalizował to w ten sposób”. Ale przeczytałem również ten argument, że konwersja ToLower vs ToUpper zależy od tego, co twoje ciągi zawierają więcej, i że zazwyczaj łańcuchy zawierają więcej małych liter, co sprawia, że ​​ToLower jest bardziej wydajne.

W szczególności chciałbym wiedzieć:

  • Czy istnieje sposób na zoptymalizowanie ToUpper lub ToLower tak, aby jeden był szybszy od drugiego?
  • Czy szybsze jest porównywanie wielkich i małych liter bez rozróżniania liter i dlaczego?
  • Czy są jakieś środowiska programistyczne (np. C, C #, Python, cokolwiek), w których jeden przypadek jest wyraźnie lepszy od drugiego i dlaczego?

Odpowiedzi:


90

Zamiana na wielkie lub małe litery w celu dokonywania porównań bez uwzględniania wielkości liter jest niepoprawna ze względu na „interesujące” cechy niektórych kultur, zwłaszcza Turcji. Zamiast tego użyj StringComparer z odpowiednimi opcjami.

MSDN zawiera świetne wskazówki dotyczące obsługi ciągów. Możesz również sprawdzić, czy Twój kod przeszedł pomyślnie test Turcji .

EDYCJA: Zwróć uwagę na komentarz Neila na temat porównań porządkowych bez rozróżniania wielkości liter. Cała ta sfera jest dość mroczna :(


15
Tak, StringComparer jest świetny, ale nie ma odpowiedzi na pytanie ... W sytuacjach, w których nie można użyć StringComparer, takich jak instrukcja swtich przeciwko łańcuchowi; powinienem ToUpper czy ToLower w przełączniku?
joshperry

7
Użyj StringComparer i "if" / "else" zamiast używania ToUpper lub ToLower.
Jon Skeet

5
John, wiem, że konwersja na małe litery jest nieprawidłowa, ale nie słyszałem, że konwersja na wielkie litery jest nieprawidłowa. Czy możesz podać przykład lub odniesienie? Artykuł MSDN, do którego utworzyłeś łącze, mówi: „Porównania wykonane przy użyciu OrdinalIgnoreCase są behawioralnie zestawieniem dwóch wywołań: wywoływania ToUpperInvariant dla obu argumentów ciągu i wykonywania porównania porządkowego”. W sekcji zatytułowanej „Operacje na łańcuchach porządkowych” przedstawia to ponownie w kodzie.
Neil

2
@Neil: Interesujące, nie widziałem tego kawałka. W przypadku porządkowego porównania bez rozróżniania wielkości liter, myślę, że to wystarczy. W końcu trzeba coś wybrać . W przypadku porównań uwzględniających różnice kulturowe bez rozróżniania wielkości liter, myślę, że nadal byłoby miejsce na jakieś dziwne zachowanie. Zwrócę uwagę na Twój komentarz w odpowiedzi ...
Jon Skeet

4
@Triynko: Myślę, że ważne jest, aby skoncentrować się przede wszystkim na poprawności, z tym, że szybkie uzyskanie błędnej odpowiedzi nie jest zwykle lepsze (a czasem gorsze) niż powolne uzyskiwanie złej odpowiedzi.
Jon Skeet

25

Od firmy Microsoft w witrynie MSDN:

Najważniejsze wskazówki dotyczące używania ciągów znaków w programie .NET Framework

Zalecenia dotyczące użycia ciągów

Czemu? Od firmy Microsoft :

Normalizuj ciągi do wielkich liter

Istnieje niewielka grupa znaków, które po przekonwertowaniu na małe litery nie mogą odbywać podróży w obie strony.

Jaki jest przykład takiej postaci, która nie może odbyć podróży w obie strony?

  • Początek : grecki symbol Rho (U + 03f1) ϱ
  • Wielkie litery: duże greckie Rho (U + 03a1) Ρ
  • Małe litery: małe greckie Rho (U + 03c1) ρ

ϱ, Ρ , ρ

.NET Fiddle

Original: ϱ
ToUpper: Ρ
ToLower: ρ

Dlatego też, jeśli chcesz dokonywać porównań bez rozróżniania wielkości liter, konwertujesz ciągi na wielkie, a nie małe litery.

Więc jeśli musisz wybrać jedną, wybierz wielkie litery .


i jaki jest tego powód?
bjan

@bjan Powodem jest to, że źle jest tego nie robić.
Ian Boyd

1
Jaka grupa postaci? Co w ogóle oznacza podróż w obie strony?
johv,

1
@johv Z linku: „Podróż w obie strony oznacza konwersję znaków z jednego ustawienia regionalnego do innego, które inaczej reprezentuje dane znakowe, a następnie dokładne pobranie oryginalnych znaków z przekonwertowanych znaków.” Jaka grupa postaci? Nie wiem, ale zgadnę małą literę ipo turecku, kiedy stanie się İ, a nie to I, do czego jesteś przyzwyczajony. Ponadto jesteśmy przyzwyczajeni do Istawania się wielkimi literami i, ale w Turcji tak się dzieje ı.
Ian Boyd

3
Wracając do odpowiedzi na pierwotne pytanie: istnieją języki znające więcej niż jeden wariant z małymi literami dla jednego wariantu z dużymi literami. Chyba że znasz zasady, kiedy użyć której reprezentacji (inny przykład w języku greckim: mała litera sigma, używasz σ na początku słowa lub w środku, ς na końcu słowa (patrz en.wikipedia.org/wiki/Sigma ), nie można bezpiecznie wrócić do wariantu z małymi literami
Aconcagua,

19

Według MSDN bardziej wydajne jest przekazywanie łańcuchów i nakazanie porównaniu ignorowania wielkości liter:

String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) jest równoważne ( ale szybsze niż ) wywołanie

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

Te porównania są nadal bardzo szybkie.

Oczywiście, jeśli w kółko porównujesz jeden ciąg, może to się nie udać.


12

Opierając się na łańcuchach, które mają zwykle więcej wpisów z małych liter, ToLower powinien teoretycznie być szybszy (dużo porównań, ale niewiele przypisań).

W C lub w przypadku korzystania z indywidualnie dostępnych elementów każdego ciągu (takich jak ciągi C lub typ ciągu STL w C ++), jest to w rzeczywistości porównanie bajtów - więc porównywanie UPPERnie różni się od lower.

Gdybyś był podstępny i longzamiast tego załadował łańcuchy do tablic, uzyskałbyś bardzo szybkie porównanie całego ciągu, ponieważ może on porównać 4 bajty naraz. Jednak czas ładowania może sprawić, że nie będzie to opłacalne.

Dlaczego musisz wiedzieć, który jest szybszy? O ile nie robisz metrycznego zestawu porównań, jeden działający kilka cykli szybciej nie ma znaczenia dla ogólnej szybkości wykonywania i brzmi jak przedwczesna optymalizacja :)


11
Aby odpowiedzieć na pytanie, dlaczego muszę wiedzieć, co jest szybsze: nie muszę wiedzieć, po prostu chcę wiedzieć. :) To po prostu przypadek zobaczenia, jak ktoś zgłasza roszczenie (np. „Porównywanie ciągów z wielkich liter jest szybsze!”) I chce wiedzieć, czy to naprawdę prawda i / lub dlaczego tak twierdził.
Parappa

1
to ma sens - ja też jestem wiecznie ciekawy takich rzeczy :)
warren

W przypadku łańcuchów C, aby przekonwertować si przekształcić tw tablice longs takie, że łańcuchy są równe, jeśli tablice są równe, musisz iść w dół s i t, aż znajdziesz '\0'znak kończący (inaczej możesz porównać śmieci za końcami łańcuchów, który może być nielegalnym dostępem do pamięci, który wywołuje niezdefiniowane zachowanie). Ale w takim razie dlaczego nie zrobić po prostu porównań, przechodząc po postaciach jeden po drugim? Z napisami C ++ można prawdopodobnie uzyskać długość i .c_str()rzutować na a long *i porównać przedrostek długości .size() - .size()%(sizeof long). Mimo wszystko, wygląda na podejrzanego.
Jonas Kölker

6

Microsoft zoptymalizował ToUpperInvariant(), nie ToUpper(). Różnica polega na tym, że niezmiennik jest bardziej przyjazny dla kultury. Jeśli musisz wykonać porównania bez uwzględniania wielkości liter w ciągach, które mogą się różnić w kulturze, użyj niezmiennej, w przeciwnym razie wykonanie niezmiennej konwersji nie powinno mieć znaczenia.

Nie mogę jednak powiedzieć, czy ToUpper () czy ToLower () jest szybsze. Nigdy tego nie próbowałem, ponieważ nigdy nie miałem sytuacji, w której wydajność miałaby tak duże znaczenie.


jeśli Microsoft zoptymalizował kod do przeprowadzania porównań wielkich liter, czy to dlatego, że kod ASCII dla dużych liter zawiera tylko dwie cyfry 65-90, podczas gdy kod ASCII Małe litery 97-122, który zawiera 3 cyfry (potrzeba więcej przetwarzania)?
Medo Medo

3
@Medo Nie pamiętam dokładnych powodów optymalizacji, ale cyfry 2 na 3 prawie na pewno nie są powodem, ponieważ wszystkie litery są przechowywane jako liczby binarne, więc cyfry dziesiętne tak naprawdę nie mają znaczenia w oparciu o sposób ich przechowywania.
Dan Herbert

4

Jeśli wykonujesz porównanie ciągów w C #, użycie .Equals () jest znacznie szybsze zamiast konwertowania obu ciągów na wielkie lub małe litery. Kolejnym dużym plusem używania .Equals () jest to, że więcej pamięci nie jest przydzielane dla 2 nowych ciągów wielkich / małych liter.


4
A jako bonus, jeśli wybierzesz odpowiednią opcję, faktycznie da to poprawne wyniki :)
Jon Skeet,

1

To naprawdę nie powinno mieć znaczenia. W przypadku znaków ASCII zdecydowanie nie ma to znaczenia - to tylko kilka porównań i trochę odwrócenie w dowolnym kierunku. Unicode może być nieco bardziej skomplikowany, ponieważ istnieją znaki, które zmieniają wielkość liter w dziwny sposób, ale tak naprawdę nie powinno być żadnej różnicy, chyba że tekst jest pełen tych znaków specjalnych.


1

Jeśli zrobisz to dobrze, konwersja na małe litery powinna mieć niewielką, nieznaczną przewagę szybkości, ale jest to, jak wielu sugerowało, zależne od kultury i nie jest dziedziczone w funkcji, ale w konwertowanych ciągach (wiele małych liter oznacza kilka przypisań do pamięci) - konwersja na duże litery jest szybsza, jeśli masz ciąg z dużą ilością dużych liter.


0

To zależy. Jak stwierdzono powyżej, zwykły tylko ASCII, jest identyczny. W .NET poczytaj o Stringu i używaj go, porównując jego poprawność z elementami i18n (kultury języków i unicode). Jeśli wiesz cokolwiek o prawdopodobieństwie danych wejściowych, użyj bardziej typowego przypadku.

Pamiętaj, że jeśli wykonujesz wiele porównań ciągów, długość jest doskonałym pierwszym dyskryminatorem.


-2

Jeśli masz do czynienia z czystym ASCII, to nie ma znaczenia. To tylko OR x, 32 vs AND x, 224. Unicode, nie mam pojęcia ...


4
Jest to całkowicie błędne - operacja OR z 32 działa tylko dla AZ i znaków 64-127; schrzanił wszystkie inne postacie. AND'owanie z 32 jest jeszcze bardziej błędne - wynikiem zawsze będzie 0 (nul) lub 32 (spacja).
Adam Rosenfield,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.