Найефективніший спосіб зробити перший символ рядкового рядка?


97

Який найефективніший спосіб зробити першим символом малу літеру 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, і ви можете змінити його безпосередньо.
Марк Петерс

Ну, це не відповідь, оскільки це знаходиться поза Явою, і покладається на кодування ASCII та на знання того, що символ вже є алфавітом. Це хакер старого часу:c[0] |= ' ';
Майк Данлаве


це вже інше питання
Енді

Відповіді:


123

Я перевірив перспективні підходи, використовуючи 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". Просто поділівшись тим, що колишній - це те, як весна робить своє автоматичне позначення квасолі, тому якщо вам потрібно отримати ім'я квасолі 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.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")));

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

ı

i


7

Рядки в Java незмінні, тому в будь-якому випадку буде створений новий рядок.

Ваш перший приклад, ймовірно, буде трохи ефективнішим, оскільки йому потрібно лише створити новий рядок, а не тимчасовий масив символів.


1
Власне, перший спосіб створює тимчасову String (для підрядка), яка є дорожчою, ніж масив символів.
Гарячі лизання

1
Без користі без підтверджуючих даних
Ніцан Вакарт

3

Дуже короткий і простий статичний метод архівування бажаного:

public static String decapitalizeString(String string) {
    return string == null || string.isEmpty() ? "" : Character.toLowerCase(string.charAt(0)) + string.substring(1);
}

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.