Java中将大写转换为小写和小写转换为大写的最快方法

4
这是一个关于性能的问题。我可以使用以下代码将大写字母转换为小写字母,反之亦然:
从小写字母转换为大写字母:
// 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。

2
为什么要关心如此微不足道的事情,比如大小写转换的性能? - Kayaman
1
无论你使用什么方法,它都应该比 toUpperCase()/toLowerCase() 更快。 - Eugene
1
@JacobG. 我一直怀疑位运算符更快,但我想确保这一点。 - Jaime Montoya
1
@Kayaman,我还没有确定性能热点。但是因为我还没有确定性能热点,也不想瞎猜,最安全的方法就是编写时考虑性能。即使性能提升微不足道,使用最快的版本也不会有什么损失。 - Jaime Montoya
1
@JaimeMontoya 这是一个常见的初学者错误。你试图优化由JDK提供的保证工作的代码,却不知道任何可能的性能优势。专业人士会找到适合进行性能编程的地方。我理解你的思维方式,当我十几岁开始编程时,我也曾这样想过。但由于你时间有限,你不能重写所有东西,因为你可能只会从中获得0.001%的性能优势。 - Kayaman
显示剩余15条评论
4个回答

7

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

如果您查看String.toLowerCase(),您会注意到其中有很多逻辑,因此,如果您正在处理大量仅限于ASCII字符的软件,并且没有其他字符,那么使用更直接的方法可能会有所好处。

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


我正在运行一个非常敏感的应用程序,位运算相对于String.toLowerCase给了我明显的性能提升。 - Coder
1
@JohnD,你能否在Jacob G的回答中运行JMH基准测试,并查看结果是否相似?如果性能提升是由于环境差异(JDK等)引起的,那么这些结果可能很有趣,因此您可以将它们发布为另一个答案。 - Kayaman
说实话,我在leetcode上参加编程比赛时使用了32位的位运算,比String.toLowerCase更快7毫秒。Leetcode有排行榜,所以我想他们已经注意到这一点了,但无论如何,这个改变让我从只超过46%的解决方案中脱颖而出,向超过98%的提交者迈进。 - Coder

5

如承诺的那样,这里有两个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#toUpperCaseCharacter#toLowerCase 执行逻辑检查(正如我今天早些时候在评论中提到的),所以你的位运算方法更快。


我认为按位版本应该进行某种检查,以查看字符是否确实需要被处理(我在谈论像 if(c >= 'a' && c <='z') 这样的东西),从而使比较更加类似和真实。虽然我很惊讶按位版本并没有比这个更快。 - Kayaman

5

你的代码只适用于ANSII字符。对于那些没有明确大小写转换的语言,比如德语中的ß(如果我说错了请纠正我,我的德语很糟糕),或者当一个字母/符号使用多字节UTF-8代码点编写时,该怎么办呢?正确性优先于性能,如果你需要处理UTF-8,问题就不那么简单了,这在String.toLowerCase(Locale)方法中表现得非常明显。


2
参数是String,迭代是在其包含的char上进行的,因此编码为UTF-16,而不是UTF-8。但你说得对,位翻转对Unicode的50442个字符中的许多字符都不起作用。 - Tom Blodget
在我提问所使用的代码中,编码是UTF-16以支持Unicode。请注意,我在代码中使用了值为65503的十进制数,其二进制表示为:‭1111111111011111‬。这是16位而不是8位,意味着它是用于Unicode的UTF-16编码。 - Jaime Montoya

4

只需使用提供的方法.toLowerCase().toUpperCase()即可。添加两个分别执行已由java.lang提供的方法的类是多余的,会使程序变慢(虽然影响很小)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接