Il modo più efficiente per rendere il primo carattere di una stringa minuscola?


97

Qual è il modo più efficiente per trasformare il primo carattere in Stringminuscolo?

Posso pensare a diversi modi per farlo:

Usando charAt()consubstring()

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

O usando un chararray

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

Sono sicuro che ci sono molti altri ottimi modi per raggiungere questo obiettivo. Che cosa mi consiglia?


Il modo migliore sarebbe modificare le tue esigenze, se possibile. Accetta uno StringBuilder invece di uno String e puoi modificarlo direttamente.
Mark Peters

Bene, questa non è una risposta perché è al di fuori di Java e si basa sulla codifica ASCII e sul sapere che il carattere è già alfabetico. È un trucco da c[0] |= ' ';
veterani


questa è una domanda diversa
Andy

Risposte:


123

Ho testato gli approcci promettenti utilizzando JMH . Codice benchmark completo .

Assunzione durante i test (per evitare di controllare ogni volta i casi d'angolo): la lunghezza della stringa di input è sempre maggiore di 1.

Risultati

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

Il punteggio sono operazioni al secondo, più sono e meglio è.

Test

  1. test1 è stato il primo approccio di Andy e Hllink:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2era il secondo approccio di Andy. È Introspector.decapitalize()suggerito anche da Daniel, ma senza due ifaffermazioni. Il primo è ifstato rimosso a causa dell'ipotesi di test. Il secondo è stato rimosso, perché violava la correttezza (cioè l'input "HI"sarebbe tornato "HI"). Questo è stato quasi il più veloce.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
  3. test3era una modifica di test2, ma invece di Character.toLowerCase(), stavo aggiungendo 32, che funziona correttamente se e solo se la stringa è in ASCII. Questo è stato il più veloce. c[0] |= ' 'dal commento di Mike ha dato la stessa performance.

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

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
  5. test5ha utilizzato due substring()chiamate.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6usa la riflessione per cambiare char value[]direttamente in String. Questo è stato il più 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();
    }

Conclusioni

Se la lunghezza della stringa è sempre maggiore di 0, utilizzare test2.

In caso contrario, dobbiamo controllare i casi d'angolo:

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

Se sei sicuro che il tuo testo sarà sempre in ASCII e stai cercando prestazioni estreme perché hai trovato questo codice nel collo di bottiglia, usa test3.


95

Mi sono imbattuto in una bella alternativa se non vuoi usare una libreria di terze parti:

import java.beans.Introspector;

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

14
Dal documento per questo metodo: "Questo normalmente significa convertire il primo carattere da maiuscolo a minuscolo, ma nel caso speciale (insolito) quando c'è più di un carattere e sia il primo che il secondo carattere sono maiuscoli, lasciamo solo. "
Andy

1
Inoltre, guardando la fonte, una volta che questo metodo gestisce il caso speciale che ho descritto nel commento precedente, utilizza semplicemente l'array di caratteri come avevo menzionato nella mia domanda.
Andy

2
Esattamente quello di cui avevo bisogno. Introspector.decapitalize ("ABC") sarà ancora ABC. WordUtils.uncapitalize ("ABC") produce "aBC". Solo condividendo che il primo è il modo in cui spring fa la sua denominazione automatica dei bean, quindi se è necessario recuperare il nome del bean ABCService, non è aBCService, ma ABCService ancora.
abitante del villaggio

21

Quando si tratta di manipolare le stringhe, dai un'occhiata a Jakarta Commons Lang StringUtils .


8
Più specificamente, il metodo uncapitalize (java.lang.String) utilizzando StringUtils ha l'ulteriore vantaggio di non doversi preoccupare delle NullPointerExceptions nel codice.
hexium

3
Non necessariamente il più efficiente, ma forse il più chiaro, che conta molto.
David Gelhar

2
Dipende dalla risorsa che stai rendendo più efficiente: CPU o tempo del programmatore :)
Dan Gravell

15

Se vuoi usare Apache Commons puoi fare quanto segue:

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

Risultato: someString


3
È una soluzione carina e pulita, ma ora è deprecata, dovremmo usare il testo comune:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
dk7

10

Nonostante un approccio orientato ai caratteri, suggerirei una soluzione orientata alle stringhe. String.toLowerCase è specifico delle impostazioni internazionali, quindi terrei in considerazione questo problema. String.toLowerCaseè preferire le minuscole secondo Character.toLowerCase . Inoltre, una soluzione orientata ai caratteri non è completamente compatibile con Unicode, perché Character.toLowerCase non può gestire caratteri supplementari.

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

AGGIORNAMENTO: Come esempio di quanto sia importante l'impostazione locale, mettiamo in minuscolo Iin turco e tedesco:

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

produrrà due risultati diversi:

io

io


7

Le stringhe in Java sono immutabili, quindi in entrambi i casi verrà creata una nuova stringa.

Il tuo primo esempio sarà probabilmente leggermente più efficiente perché deve solo creare una nuova stringa e non un array di caratteri temporaneo.


1
In realtà, il primo modo crea una stringa temporanea (per sottostringa), che è più costosa dell'array di caratteri.
Hot Licks

1
Inutile senza dati di supporto
Nitsan Wakart

3

Un metodo statico molto breve e semplice per archiviare ciò che desideri:

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

2

Se quello che ti serve è molto semplice (es. Nomi di classi java, no locales), puoi anche usare la classe CaseFormat nella libreria Google Guava .

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

Oppure puoi preparare e riutilizzare un oggetto convertitore, che potrebbe essere più efficiente.

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

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

Per comprendere meglio la filosofia della manipolazione delle stringhe di Google Guava, controlla questa pagina wiki .


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

1

Mi sono imbattuto in questo solo oggi. Ho provato a farlo da solo nel modo più pedonale. Ci è voluta una riga, anche se lunga. Ecco qui

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

Dà:

Prima di str = TaxoRanks

Dopo str = taxoRanks


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

Risultato:

res4: String = hello
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.