如何最有效地将字符串的第一个字符改为小写?

131

如何高效地将字符串的第一个字符转换为小写?

我能想到几种方法:

使用 charAt()substring()

String input   = "SomeInputString";
String output  = Character.toLowerCase(input.charAt(0)) +
                   (input.length() > 1 ? input.substring(1) : "");

或者使用 char 数组

 String input  = "SomeInputString";
 char c[]      = input.toCharArray();
 c[0]          = Character.toLowerCase(c[0]);
 String output = new String(c);
我相信有许多其他很好的方法可以实现这个。你有什么建议?

最好的方法是尽可能改变您的要求。接受一个 StringBuilder 而不是 String,您可以直接修改它。 - Mark Peters
这不是一个答案,因为它超出了Java的范畴,并且依赖于ASCII编码和知道字符已经是字母的情况。这是老程序员的技巧:c[0] |= ' '; - Mike Dunlavey
1
那是一个不同的问题。 - Andy
11个回答

160

我使用JMH测试了一些有潜力的方法。完整的基准测试代码

在测试过程中的假设(为了避免每次都检查边界情况):输入字符串的长度始终大于1。

结果

Benchmark           Mode  Cnt         Score        Error  Units
MyBenchmark.test1  thrpt   20  10463220.493 ± 288805.068  ops/s
MyBenchmark.test2  thrpt   20  14730158.709 ± 530444.444  ops/s
MyBenchmark.test3  thrpt   20  16079551.751 ±  56884.357  ops/s
MyBenchmark.test4  thrpt   20   9762578.446 ± 584316.582  ops/s
MyBenchmark.test5  thrpt   20   6093216.066 ± 180062.872  ops/s
MyBenchmark.test6  thrpt   20   2104102.578 ±  18705.805  ops/s

分数是每秒操作次数,越多越好。

测试

  1. test1 是 Andy 和 Hllink 的第一种方法:

     string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
    
  2. test2 是 Andy 的第二种方法。这也是 Daniel 建议的 Introspector.decapitalize(),但没有两个 if 语句。第一个 if 被删除了,因为测试假设。第二个被删除了,因为它违反了正确性(即输入 "HI" 将返回 "HI")。这几乎是最快的。

     char c[] = string.toCharArray();
     c[0] = Character.toLowerCase(c[0]);
     string = new String(c);
    
  3. test3 是对 test2 的修改,但我使用的是加上 32 而不是 Character.toLowerCase(),只有当字符串在 ASCII 中时才能正常工作。这是最快的。Mike 的 评论 中的 c[0] |= ' ' 给出了相同的性能。

     char c[] = string.toCharArray();
     c[0] += 32;
     string = new String(c);
    
  4. test4 使用了 StringBuilder

     StringBuilder sb = new StringBuilder(string);
     sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
     string = sb.toString();
    
  5. test5 使用了两个 substring() 调用。

     string = string.substring(0, 1).toLowerCase() + string.substring(1);
    
  6. test6 使用反射直接更改 char value[] 在 String 中。这是最慢的。

     try {
         Field field = String.class.getDeclaredField("value");
         field.setAccessible(true);
         char[] value = (char[]) field.get(string);
         value[0] = Character.toLowerCase(value[0]);
     } catch (IllegalAccessException e) {
         e.printStackTrace();
     } catch (NoSuchFieldException e) {
         e.printStackTrace();
     }
    

结论

如果字符串长度始终大于0,则使用test2

如果不是,则我们必须检查边界情况:

public static String decapitalize(String string) {
    if (string == null || string.length() == 0) {
        return string;
    }

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);

    return new String(c);
}

如果您确定您的文本始终为ASCII,并且您正在寻找极致性能,因为您在瓶颈中发现了此代码,请使用test3


103

如果你不想使用第三方库,我发现了一个不错的选择:

import java.beans.Introspector;

Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));

19
根据这个方法的文档:“通常情况下,这意味着将第一个字符从大写转换为小写,但在(不寻常的)特殊情况下,如果有多个字符且第一个和第二个字符都是大写字母,我们会保持不变。” - Andy
1
此外,从源代码来看,一旦这个方法处理了我在之前评论中描述的特殊情况,它仅仅使用了字符数组,就像我在问题中提到的那样。 - Andy
4
正是我需要的。Introspector.decapitalize("ABC")仍将是ABC。WordUtils.uncapitalize("ABC")会产生"aBC"。只是分享一下,前者是Spring自动命名bean的方式,所以如果您需要按bean名称检索ABCService,则不是aBCService,而是ABCService。 - lorraine batol

25

提到字符串操作,可以看看 Jakarta Commons Lang 的 StringUtils


9
具体来说,使用StringUtils的uncapitalize(java.lang.String)方法具有额外的优点,您无需担心代码中的NullPointerException问题。 - hexium
3
可能并不是最高效的,但或许是最清晰的,这点很重要。 - David Gelhar
2
取决于您要使哪种资源更有效率——CPU 还是程序员时间 :) - Dan Gravell

20

如果你想使用Apache Commons,你可以按照以下步骤:

import org.apache.commons.lang3.text.WordUtils;
[...] 
String s = "SomeString"; 
String firstLower = WordUtils.uncapitalize(s);

结果:someString


3
这是一个简洁而清晰的解决方案,但现在已经过时了,我们应该使用commons-text库:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'。请注意,这里不提供任何解释或其他内容。 - dk7

10
尽管采用字符导向的方法,我建议使用基于字符串的解决方案。 String.toLowerCase 是区域特定的,因此我会考虑这个问题。 根据Character.toLowerCase,应该首选String.toLowerCase 以进行小写处理。 此外,采用字符导向的解决方案不完全与Unicode兼容,因为Character.toLowerCase无法处理补充字符。
public static final String uncapitalize(final String originalStr,
            final Locale locale) {
        final int splitIndex = 1;
        final String result;
        if (originalStr.isEmpty()) {
        result = originalStr;
        } else {
        final String first = originalStr.substring(0, splitIndex).toLowerCase(
                locale);
        final String rest = originalStr.substring(splitIndex);
        final StringBuilder uncapStr = new StringBuilder(first).append(rest);
        result = uncapStr.toString();
        }
        return result;
    }

更新: 举个例子,说明语言环境设置的重要性,让我们来看一下在土耳其语和德语中如何将大写字母 I 转换为小写字母:

System.out.println(uncapitalize("I", new Locale("TR","tr")));
System.out.println(uncapitalize("I", new Locale("DE","de")));

会输出两个不同的结果:

ı

i


7

Java中的字符串是不可变的,因此无论哪种方式都会创建一个新的字符串。

您的第一个示例可能会稍微更有效,因为它只需要创建一个新的字符串而不是临时字符数组。


1
实际上,第一种方法创建了一个临时字符串(用于子字符串),这比字符数组更昂贵。 - Hot Licks
1
没有支持数据就毫无帮助。 - Nitsan Wakart

4
一个非常简短而简单的静态方法来存档您想要的内容:
public static String decapitalizeString(String string) {
    return string == null || string.isEmpty() ? "" : Character.toLowerCase(string.charAt(0)) + string.substring(1);
}

3
val str = "Hello"
s"${str.head.toLower}${str.tail}"

结果:

res4: String = hello

2

如果您需要的只是非常简单的内容(例如Java类名称,没有本地化),您也可以使用CaseFormat类在Google Guava库中。

String converted = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, "FooBar");
assertEquals("fooBar", converted);

或者您可以准备并重复使用一个转换器对象,这可能更有效。

Converter<String, String> converter=
    CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.LOWER_CAMEL);

assertEquals("fooBar", converter.convert("FooBar"));

为了更好地理解Google Guava字符串操作的哲学,请查看此wiki页面。 (链接)

1
String testString = "SomeInputString";
String firstLetter = testString.substring(0,1).toLowerCase();
String restLetters = testString.substring(1);
String resultString = firstLetter + restLetters;

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