¿La forma más eficiente de convertir el primer carácter de una cadena en minúsculas?

97

¿Cuál es la forma más eficaz de hacer el primer carácter de una Stringminúscula?

Puedo pensar en varias formas de hacer esto:

Usando charAt()consubstring()

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

O usando una charmatriz

 String input  = "SomeInputString";
 char c[]      = input.toCharArray();
 c[0]          = Character.toLowerCase(c[0]);
 String output = new String(c);

Estoy seguro de que hay muchas otras formas excelentes de lograrlo. ¿Que recomiendas?

Andy
fuente
La mejor manera sería cambiar sus requisitos si es posible. Acepte StringBuilder en lugar de String y podrá modificarlo directamente.
Mark Peters
Bueno, esta no es una respuesta porque está fuera de Java y se basa en la codificación ASCII y en saber que el carácter ya es alfabético. Es el truco de un veterano:c[0] |= ' ';
Mike Dunlavey
posible duplicado de Conversión a mayúsculas y minúsculas en Java
Raedwald
esa es una pregunta diferente
Andy

Respuestas:

123

Probé los enfoques prometedores utilizando JMH . Código de referencia completo .

Supuesto durante las pruebas (para evitar comprobar los casos de las esquinas cada vez): la longitud de la cadena de entrada es siempre mayor que 1.

Resultados

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

La puntuación son operaciones por segundo, cuanto más, mejor.

Pruebas

  1. test1 fue el primer enfoque de Andy e Hllink:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2fue el segundo acercamiento de Andy. También lo Introspector.decapitalize()sugiere Daniel, pero sin dos ifdeclaraciones. Primero ifse eliminó debido al supuesto de prueba. El segundo fue eliminado porque violaba la corrección (es decir, la entrada "HI"volvería "HI"). Este fue casi el más rápido.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
  3. test3era una modificación de test2, pero en lugar de Character.toLowerCase(), estaba agregando 32, que funciona correctamente si y solo si la cadena está en ASCII. Este fue el más rápido. c[0] |= ' 'del comentario de Mike dio la misma actuación.

    char c[] = string.toCharArray();
    c[0] += 32;
    string = new String(c);
  4. test4utilizado StringBuilder.

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
  5. test5utilizó dos substring()llamadas.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6usa la reflexión para cambiar char value[]directamente en String. Este fue el más lento.

    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();
    }

Conclusiones

Si la longitud de la cadena es siempre mayor que 0, utilice test2.

Si no es así, tenemos que comprobar los casos de las esquinas:

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);
}

Si está seguro de que su texto siempre estará en ASCII y está buscando un rendimiento extremo porque encontró este código en el cuello de botella, use test3.

Adam Stelmaszczyk
fuente
95

Encontré una buena alternativa si no desea utilizar una biblioteca de terceros:

import java.beans.Introspector;

Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));
Daniel Pacak
fuente
14
Del documento para este método: "Esto normalmente significa convertir el primer carácter de mayúsculas a minúsculas, pero en el caso especial (inusual) cuando hay más de un carácter y tanto el primer como el segundo caracteres son mayúsculas, dejamos solo ".
Andy
1
Además, mirando la fuente, una vez que este método maneja el caso especial que describí en el comentario anterior, simplemente usa la matriz de caracteres como mencioné en mi pregunta.
Andy
2
Exactamente lo que necesitaba. Introspector.decapitalize ("ABC") seguirá siendo ABC. WordUtils.uncapitalize ("ABC") produce "aBC". Solo compartiendo que lo primero es cómo Spring hace su nombre automático de beans, por lo que si necesita recuperar por nombre de bean el ABCService, no es unBCService, pero ABCService todavía.
aldeano
21

Cuando se trata de manipulación de cadenas, eche un vistazo a Jakarta Commons Lang StringUtils .

Carlos Tasada
fuente
8
Más específicamente, el método uncapitalize (java.lang.String) Using StringUtils tiene la ventaja adicional de no tener que preocuparse por NullPointerExceptions en su código.
hexio
3
No necesariamente el más eficiente, pero quizás el más claro, lo que cuenta mucho.
David Gelhar
2
Depende del recurso que esté haciendo más eficiente: CPU o tiempo del programador :)
Dan Gravell
15

Si desea utilizar Apache Commons, puede hacer lo siguiente:

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

Resultado: someString

Sebastián
fuente
3
Es una solución agradable y limpia, pero ahora está en desuso, deberíamos usar commons-text:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
dk7
10

A pesar de un enfoque orientado a caracteres, sugeriría una solución orientada a cadenas. String.toLowerCase es específico de la configuración regional, por lo que tomaría este problema en cuenta. String.toLowerCasees preferir las minúsculas de acuerdo con Character.toLowerCase . Además, una solución orientada a caracteres no es totalmente compatible con Unicode, porque Character.toLowerCase no puede manejar caracteres suplementarios.

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;
    }

ACTUALIZACIÓN: Como ejemplo, cuán importante es la configuración regional, déjenos en minúsculas Ien turco y alemán:

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

generará dos resultados diferentes:

yo

yo

Michael Konietzka
fuente
7

Las cadenas en Java son inmutables, por lo que de cualquier manera se creará una nueva cadena.

Su primer ejemplo probablemente será un poco más eficiente porque solo necesita crear una nueva cadena y no una matriz de caracteres temporal.

Alan Geleynse
fuente
1
En realidad, la primera forma crea una cadena temporal (para subcadena), que es más cara que la matriz de caracteres.
Hot Licks
1
Inútil sin datos de apoyo
Nitsan Wakart
3

Un método estático muy corto y simple para archivar lo que desea:

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

Si lo que necesita es muy simple (por ejemplo, nombres de clases java, sin configuraciones regionales), también puede usar la clase CaseFormat en la biblioteca de Google Guava .

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

O puede preparar y reutilizar un objeto convertidor, que podría ser más eficiente.

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

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

Para comprender mejor la filosofía de la manipulación de cadenas de Google Guava, consulte esta página wiki .

Peter Lamberg
fuente
1
String testString = "SomeInputString";
String firstLetter = testString.substring(0,1).toLowerCase();
String restLetters = testString.substring(1);
String resultString = firstLetter + restLetters;
Bae Cheol Shin
fuente
1

Me he encontrado con esto solo hoy. Intenté hacerlo yo mismo de la manera más peatonal. Eso tomó una línea, aunque demasiado larga. Aquí va

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);

Da:

Antes str = TaxoRanks

Después de str = taxoRanks

usuario3501758
fuente
1
val str = "Hello"
s"${str.head.toLower}${str.tail}"

Resultado:

res4: String = hello
Vivek
fuente