Быстрый способ преобразования прописных букв в строчные и строчных в прописные на языке 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   2  

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).