Самый эффективный способ сделать первый символ строчного нижнего регистра?

101

Какой самый эффективный способ сделать первый символ Stringнижнего регистра?

Я могу придумать несколько способов сделать это:

Использование 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, и вы можете изменить его напрямую.
Марк Питерс,
Что ж, это не ответ, потому что он находится за пределами Java и полагается на кодировку ASCII и знание того, что символ уже является буквенным. Это старый хакер:c[0] |= ' ';
Майк Данлэйви,
это другой вопрос
Энди

Ответы:

127

Я протестировал перспективные подходы с помощью 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 был первым подходом Энди и Хллинка:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2был второй подход Энди. Это также Introspector.decapitalize()предлагает Даниил, но без двух ifутверждений. Первый ifбыл удален из-за предположения о тестировании. Второй был удален, потому что он нарушал правильность (т.е. ввод "HI"возвращался "HI"). Это было почти самым быстрым.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
  3. test3был модификацией test2, но вместо этого Character.toLowerCase()я добавил 32, который работает правильно тогда и только тогда, когда строка находится в ASCII. Это было самым быстрым. 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.

Адам Стельмашчик
источник
95

Я наткнулся на хорошую альтернативу, если вы не хотите использовать стороннюю библиотеку:

import java.beans.Introspector;

Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));
Даниэль Пакак
источник
14
Из документа по этому методу: «Обычно это означает преобразование первого символа из верхнего регистра в нижний регистр, но в (необычном) особом случае, когда существует более одного символа, а первый и второй символы - в верхнем регистре, мы оставляем только это. "
Энди
1
Кроме того, глядя на источник, как только этот метод обрабатывает особый случай, описанный в предыдущем комментарии, он просто использует массив char, как я упоминал в своем вопросе.
Энди
2
Именно то, что мне нужно. Introspector.decapitalize («ABC») по-прежнему будет ABC. WordUtils.uncapitalize («ABC») производит «aBC». Просто поделитесь тем, что первое - это то, как spring выполняет автоматическое именование bean-компонентов, поэтому, если вам нужно получить по имени bean-компонента ABCService, это не aBCService, но все же ABCService.
сельский житель
21

Когда дело доходит до манипуляций со строками, обратите внимание на Jakarta Commons Lang StringUtils .

Карлос Тасада
источник
8
В частности, метод uncapitalize (java.lang.String) с использованием StringUtils имеет дополнительное преимущество, заключающееся в том, что вам не нужно беспокоиться о NullPointerExceptions в вашем коде.
гексий
3
Не обязательно самый эффективный, но, возможно, самый четкий, что очень важно.
Дэвид Гелхар,
2
Зависит от того, какой ресурс вы делаете более эффективным - процессор или время программиста :)
Дэн
15

Если вы хотите использовать 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. String.toLowerCase зависит от локали , поэтому я хотел бы принять во внимание эту проблему. String.toLowerCaseпредпочтительнее использовать строчные буквы в соответствии с Character.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")));

выведет два разных результата:

я

я

Майкл Конецка
источник
7

Строки в Java неизменяемы, поэтому в любом случае будет создана новая строка.

Ваш первый пример, вероятно, будет немного более эффективным, потому что ему нужно создать только новую строку, а не временный массив символов.

Алан Гелейнсе
источник
1
Фактически, первый способ создает временную строку (для подстроки), которая дороже, чем массив символов.
Hot Licks
1
Бесполезно без подтверждающих данных
Nitsan Wakart
3

Очень короткий и простой статический метод архивирования того, что вы хотите:

public static String decapitalizeString(String string) {
    return string == null || string.isEmpty() ? "" : Character.toLowerCase(string.charAt(0)) + string.substring(1);
}
Hllink
источник
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, посетите эту страницу вики .

Питер Ламберг
источник
1
String testString = "SomeInputString";
String firstLetter = testString.substring(0,1).toLowerCase();
String restLetters = testString.substring(1);
String resultString = firstLetter + restLetters;
Пэ Чхоль Шин
источник
1

Я столкнулся с этим только сегодня. Пытался сделать сам по самой пешеходной схеме. Это заняло одну строчку, хотя и длинную. Поехали

String str = "TaxoRank"; 

System.out.println(" Before str = " + str); 

str = str.replaceFirst(str.substring(0,1), str.substring(0,1).toLowerCase());

System.out.println(" After str = " + str);

Дает:

Перед str = TaxoRanks

После str = taxoRanks

user3501758
источник
1
val str = "Hello"
s"${str.head.toLower}${str.tail}"

Результат:

res4: String = hello
Вивек
источник