Быстрый способ преобразования прописных букв в строчные и строчных в прописные на языке Java
Это вопрос о производительности. Я могу конвертировать из верхнего регистра в нижний и наоборот, используя этот код:
Из нижнего регистра в верхний:
// Uppercase letters.
class UpperCase {
public static void main(String args[]) {
char ch;
for(int i=0; i < 10; i++) {
ch = (char) ('a' + i);
System.out.print(ch);
// This statement turns off the 6th bit.
ch = (char) ((int) ch & 65503); // ch is now uppercase
System.out.print(ch + " ");
}
}
}
Из верхнего в нижний регистр:
// Lowercase letters.
class LowerCase {
public static void main(String args[]) {
char ch;
for(int i=0; i < 10; i++) {
ch = (char) ('A' + i);
System.out.print(ch);
ch = (char) ((int) ch | 32); // ch is now lowercase
System.out.print(ch + " ");
}
}
}
Я знаю, что Java предоставляет следующие методы: .toUpperCase( )
и .toLowerCase( )
. Думая о производительности, каков самый быстрый способ сделать это преобразование, используя побитовые операции, как я показал это в приведенном выше коде, или используя методы .toUpperCase( )
и .toLowerCase( )
? Благодарю ты.
[6]}правка 1: Обратите внимание, как я использую десятичное число 65503, которое является двоичным 1111111111011111. Я использую 16 бит, а не 8. В соответствии с ответом в настоящее время с большим количеством голосов на сколько бит в символе?:
Символ Unicode в кодировке UTF-16 находится между 16 (2 байта) и 32 битами (4 байта), хотя большинство распространенных символов занимают 16 бит. Это кодировка, используемая Windows внутренне.
Код в моем вопросе предполагает UTF-16.
4 ответа:
Как и было обещано, вот два критерия JMH; один сравнивает
Character#toUpperCase
с вашим побитовым методом, а другой сравниваетCharacter#toLowerCase
с вашим другим побитовым методом. Обратите внимание, что тестировались только символы в английском алфавите.Первый бенчмарк (в верхнем регистре):
@State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Fork(3) public class Test { @Param({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}) public char c; @Benchmark public char toUpperCaseNormal() { return Character.toUpperCase(c); } @Benchmark public char toUpperCaseBitwise() { return (char) (c & 65503); } }
Вывод:
Benchmark (c) Mode Cnt Score Error Units Test.toUpperCaseNormal a avgt 30 2.447 ± 0.028 ns/op Test.toUpperCaseNormal b avgt 30 2.438 ± 0.035 ns/op Test.toUpperCaseNormal c avgt 30 2.506 ± 0.083 ns/op Test.toUpperCaseNormal d avgt 30 2.411 ± 0.010 ns/op Test.toUpperCaseNormal e avgt 30 2.417 ± 0.010 ns/op Test.toUpperCaseNormal f avgt 30 2.412 ± 0.005 ns/op Test.toUpperCaseNormal g avgt 30 2.410 ± 0.004 ns/op Test.toUpperCaseBitwise a avgt 30 1.758 ± 0.007 ns/op Test.toUpperCaseBitwise b avgt 30 1.789 ± 0.032 ns/op Test.toUpperCaseBitwise c avgt 30 1.763 ± 0.005 ns/op Test.toUpperCaseBitwise d avgt 30 1.763 ± 0.012 ns/op Test.toUpperCaseBitwise e avgt 30 1.757 ± 0.003 ns/op Test.toUpperCaseBitwise f avgt 30 1.755 ± 0.003 ns/op Test.toUpperCaseBitwise g avgt 30 1.759 ± 0.003 ns/op
Второй бенчмарк (в нижнем регистре):
@State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Fork(3) public class Test { @Param({"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}) public char c; @Benchmark public char toLowerCaseNormal() { return Character.toUpperCase(c); } @Benchmark public char toLowerCaseBitwise() { return (char) (c | 32); } }
Вывод:
Benchmark (c) Mode Cnt Score Error Units Test.toLowerCaseNormal A avgt 30 2.084 ± 0.007 ns/op Test.toLowerCaseNormal B avgt 30 2.079 ± 0.006 ns/op Test.toLowerCaseNormal C avgt 30 2.081 ± 0.005 ns/op Test.toLowerCaseNormal D avgt 30 2.083 ± 0.010 ns/op Test.toLowerCaseNormal E avgt 30 2.080 ± 0.005 ns/op Test.toLowerCaseNormal F avgt 30 2.091 ± 0.020 ns/op Test.toLowerCaseNormal G avgt 30 2.116 ± 0.061 ns/op Test.toLowerCaseBitwise A avgt 30 1.708 ± 0.006 ns/op Test.toLowerCaseBitwise B avgt 30 1.705 ± 0.018 ns/op Test.toLowerCaseBitwise C avgt 30 1.721 ± 0.022 ns/op Test.toLowerCaseBitwise D avgt 30 1.718 ± 0.010 ns/op Test.toLowerCaseBitwise E avgt 30 1.706 ± 0.009 ns/op Test.toLowerCaseBitwise F avgt 30 1.704 ± 0.004 ns/op Test.toLowerCaseBitwise G avgt 30 1.711 ± 0.007 ns/op
Я включил только несколько разных букв (хотя все они были протестированы), так как все они похожи выходы.
Очевидно, что ваши побитовые методы быстрее, в основном за счет
Character#toUpperCase
иCharacter#toLowerCase
выполнения логических проверок (как я уже упоминал ранее сегодня в своем комментарии).
Да, метод, написанный вами, будет немного быстрее, если вы решите выполнить преобразование case с помощью простой побитовой операции, в то время как методы Java имеют более сложную логику для поддержки символов unicode, а не только кодировки ASCII.
Если вы посмотрите на строку .toLowerCase () Вы заметите, что там много логики, поэтому, если бы вы работали с программным обеспечением, которое должно было обрабатывать только огромное количество ASCII, и ничего больше, вы могли бы действительно увидеть некоторую выгоду от использования более прямого подхода.
Но Если вы не пишете программу, которая тратит большую часть своего времени на преобразование ASCII, вы не сможете заметить никакой разницы даже с профилировщиком (и если вы пишете такую программу...надо искать другую работу).
Просто придерживайтесь предоставленных методов
.toLowerCase()
и.toUpperCase()
. Добавление двух отдельных классов для выполнения двух методов, которые уже были предоставленыjava.lang
, является излишним и замедлит вашу программу (с небольшим запасом).
Ваш код работает только для символов ANSII. Как насчет языков, где нет четкого преобразования между строчными и прописными буквами, например, немецкий
ß
(пожалуйста, поправьте меня, если я ошибаюсь, мой немецкий ужасен) или когда буква/символ написана с использованием многобайтовой кодовой точки UTF-8. Корректность предшествует производительности, и проблема не так проста, если вы должны обрабатывать UTF-8, как это очевидно в методеString.toLowerCase(Locale)
.