【问题标题】:Fastest way of converting uppercase to lowercase and lowercase to uppercase in Java在Java中将大写转换为小写和小写转换为大写的最快方法
【发布时间】:2018-08-17 08:29:57
【问题描述】:

这是一个关于性能的问题。我可以使用以下代码从大写转换为小写,反之亦然:

从小写到大写:

// 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( ) 方法?谢谢。

编辑 1:请注意我如何使用十进制 65503,它是二进制 1111111111011111。我使用的是 16 位,而不是 8 位。根据目前在How many bits or bytes are there in a character? 获得更多投票的答案:

UTF-16 编码中的 Unicode 字符介于 16(2 个字节)和 32 位(4 个字节)之间,但大多数常见字符占用 16 位。这是 Windows 内部使用的编码。

我的问题中的代码假定为 UTF-16。

【问题讨论】:

  • 为什么要关心大小写转换这种无关紧要的事情?
  • 如果那时还没有,我将在几个小时内使用 JMH 进行基准测试。我怀疑按位运算符的性能会更好,因为Character#toLowerCase 和Character#toUpperCase 都对参数执行大量检查。
  • 无论你有什么应该都比toUpperCase()/toLowerCase()快
  • @JaimeMontoya 但你不能证明微优化是合理的。你在浪费时间做一些无关紧要的事情。或者您是否真的通过分析您的软件将案例转换确定为性能热点?
  • @JacobG。我怀疑按位运算符更快,但我想确定。

标签: java performance ascii uppercase lowercase


【解决方案1】:

只需坚持提供的方法.toLowerCase() 和.toUpperCase()。添加两个单独的类来执行 java.lang 已经提供的两种方法是一种过大的杀伤力,并且会减慢您的程序(有一点余量)。

【讨论】:

    【解决方案2】:

    您的代码仅适用于 ANSII 字符。小写和大写之间不存在明确转换的语言呢?德语 ß(如果我错了,请纠正我,我的德语很糟糕)或者使用多字节 UTF-8 代码点编写字母/符号时。正确性先于性能,如果您必须处理 UTF-8,问题就不是那么简单了,这在 String.toLowerCase(Locale) 方法中很明显。

    【讨论】:

    • 参数是String,迭代结束于它所包含的chars,所以编码是UTF-16,而不是UTF-8。但是你是对的,对于 Unicode 的许多 50442 字母来说,位翻转是行不通的。
    • 在我的问题中使用的代码中,编码是 UTF-16 以支持 Unicode。请注意我在代码中如何使用十进制值 65503,即二进制:1111111111011111。那是 16 位,而不是 8,意味着 Unicode 的 UTF-16。
    【解决方案3】:

    是的,如果您选择使用简单的按位运算执行大小写转换,您编写的方法会稍微快一些,而 Java 的方法具有更复杂的逻辑来支持 unicode 字符,而不仅仅是 ASCII 字符集。

    如果您查看String.toLowerCase(),您会注意到其中有很多逻辑,因此,如果您使用的软件只需要处理大量 ASCII,而无需其他任何东西,您实际上可能会看到一些好处避免使用更直接的方法。

    但是除非您正在编写一个大部分时间都在转换 ASCII 的程序,即使使用分析器,您也不会注意到任何差异(如果您正在编写那种程序...你应该找另一份工作)。

    【讨论】:

    • 我正在运行一个非常敏感的应用程序,与 String.toLowerCase 相比,按位运算给我带来了可衡量的性能提升
    • @JohnD 你能在 Jacob G 的回答中运行 JMH 基准测试,看看结果是否相似?如果性能提升是由于环境差异(JDK 等)引起的,那么它们可能会很有趣,因此您可以将它们作为另一个答案发布。
    • TBH 我在 leetcode 上进行编程比赛,32 位运算比 String.toLowerCase 少 7 毫秒。 Leetcode 有排行榜,所以我认为他们占了上风,但无论哪种情况,这一变化都让我从仅击败 46% 的问题解决方案到击败超过 98% 的提交
    【解决方案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 执行逻辑检查(正如我今天早些时候在评论中提到的那样)。

    【讨论】:

    • IMO 按位版本应该进行 some 类型的检查以查看字符是否确实需要处理(我说的是if(c &gt;= 'a' &amp;&amp; c &lt;='z') 之类的东西)以使比较比较相似和真实。虽然我很惊讶按位版本并没有比这更快。
    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 2011-07-21
    • 2022-01-24
    • 1970-01-01
    • 2011-10-29
    • 2013-01-25
    • 1970-01-01
    • 2017-06-30
    相关资源
    最近更新 更多