Der effizienteste Weg, um das erste Zeichen eines Strings in Kleinbuchstaben zu schreiben?

97

Was ist der effizienteste Weg, um das erste Zeichen in StringKleinbuchstaben zu schreiben?

Ich kann mir eine Reihe von Möglichkeiten vorstellen, dies zu tun:

Verwenden charAt()mitsubstring()

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

Oder mit einem charArray

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

Ich bin sicher, dass es viele andere großartige Möglichkeiten gibt, dies zu erreichen. Was empfehlen Sie?

Andy
quelle
Der beste Weg wäre, wenn möglich, Ihre Anforderungen zu ändern. Akzeptieren Sie einen StringBuilder anstelle eines Strings und Sie können ihn direkt ändern.
Mark Peters
Nun, dies ist keine Antwort, da es außerhalb von Java liegt und auf der ASCII-Codierung und dem Wissen beruht, dass das Zeichen bereits alphabetisch ist. Es ist ein Oldtimer-Hack:c[0] |= ' ';
Mike Dunlavey
Mögliches Duplikat der Konvertierung in Groß- und Kleinbuchstaben in Java
Raedwald
Das ist eine andere Frage
Andy

Antworten:

123

Ich habe die vielversprechenden Ansätze mit JMH getestet . Vollständiger Benchmark- Code .

Annahme während der Tests (um zu vermeiden, dass die Eckfälle jedes Mal überprüft werden): Die Länge der Eingabezeichenfolge ist immer größer als 1.

Ergebnisse

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

Die Punktzahl sind Operationen pro Sekunde, je mehr desto besser.

Tests

  1. test1 war zuerst Andys und Hllinks Ansatz:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2war zweiter Andys Ansatz. Es wird auch Introspector.decapitalize()von Daniel vorgeschlagen, aber ohne zwei ifAussagen. Zuerst ifwurde wegen der Testannahme entfernt. Der zweite wurde entfernt, weil er die Korrektheit verletzte (dh die Eingabe "HI"würde zurückkehren "HI"). Dies war fast der schnellste.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
  3. test3war eine Modifikation von test2, aber stattdessen habe Character.toLowerCase()ich 32 hinzugefügt, was genau dann richtig funktioniert, wenn die Zeichenfolge in ASCII ist. Dies war der schnellste. c[0] |= ' 'aus Mikes Kommentar ergab die gleiche Leistung.

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

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
  5. test5benutzte zwei substring()Anrufe.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6verwendet Reflektion, um char value[]direkt in String zu ändern . Dies war der langsamste.

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

Schlussfolgerungen

Wenn die Zeichenfolgenlänge immer größer als 0 ist, verwenden Sie test2.

Wenn nicht, müssen wir die Eckfälle überprüfen:

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

Wenn Sie sicher sind, dass Ihr Text immer in ASCII vorliegt und Sie nach extremer Leistung suchen, weil Sie diesen Code im Engpass gefunden haben, verwenden Sie test3.

Adam Stelmaszczyk
quelle
95

Ich bin auf eine nette Alternative gestoßen, wenn Sie keine Bibliothek eines Drittanbieters verwenden möchten:

import java.beans.Introspector;

Assert.assertEquals("someInputString", Introspector.decapitalize("SomeInputString"));
Daniel Pacak
quelle
14
Aus dem Dokument für diese Methode: "Dies bedeutet normalerweise, dass das erste Zeichen von Groß- in Kleinbuchstaben konvertiert wird. In dem (ungewöhnlichen) Sonderfall, in dem mehr als ein Zeichen vorhanden ist und sowohl das erste als auch das zweite Zeichen in Großbuchstaben geschrieben sind, verlassen wir das Dokument es allein. "
Andy
1
Wenn diese Methode den Quellfall behandelt, den ich im vorherigen Kommentar beschrieben habe, verwendet sie in der Quelle lediglich das char-Array, wie ich es in meiner Frage erwähnt habe.
Andy
2
Genau das, was ich brauchte. Introspector.decapitalize ("ABC") ist weiterhin ABC. WordUtils.uncapitalize ("ABC") erzeugt "aBC". Teilen Sie nur mit, dass erstere die automatische Benennung von Beans durch Spring bewirkt. Wenn Sie also den ABCService anhand des Bean-Namens abrufen müssen, handelt es sich nicht um einen BCService, sondern um ABCService.
Dorfbewohner
20

Wenn es um die Manipulation von Strings geht, werfen Sie einen Blick auf Jakarta Commons Lang StringUtils .

Carlos Tasada
quelle
7
Insbesondere hat die Methode uncapitalize (java.lang.String) Using StringUtils den zusätzlichen Vorteil, dass Sie sich keine Gedanken über NullPointerExceptions in Ihrem Code machen müssen.
Hexium
3
Nicht unbedingt das effizienteste, aber vielleicht das klarste, was sehr wichtig ist.
David Gelhar
2
Hängt davon ab, welche Ressource Sie effizienter machen - CPU- oder Programmiererzeit :)
Dan Gravell
15

Wenn Sie Apache Commons verwenden möchten, können Sie Folgendes tun:

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

Ergebnis: someString

Sebastian
quelle
3
Es ist eine schöne und saubere Lösung, aber diese ist jetzt veraltet. Wir sollten Commons-Text verwenden:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
dk7
10

Trotz eines char-orientierten Ansatzes würde ich eine String-orientierte Lösung vorschlagen. String.toLowerCase ist länderspezifisch, daher würde ich dieses Problem berücksichtigen. String.toLowerCaseist für Kleinbuchstaben nach Character.toLowerCase zu bevorzugen . Eine char-orientierte Lösung ist auch nicht vollständig Unicode-kompatibel, da Character.toLowerCase keine zusätzlichen Zeichen verarbeiten kann.

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

UPDATE: Als Beispiel, wie wichtig die Ländereinstellung ist, lassen Sie uns Iin Türkisch und Deutsch Kleinbuchstaben schreiben:

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

gibt zwei verschiedene Ergebnisse aus:

ich

ich

Michael Konietzka
quelle
7

Zeichenfolgen in Java sind unveränderlich, sodass in beiden Fällen eine neue Zeichenfolge erstellt wird.

Ihr erstes Beispiel wird wahrscheinlich etwas effizienter sein, da nur eine neue Zeichenfolge und kein temporäres Zeichenarray erstellt werden muss.

Alan Geleynse
quelle
1
Tatsächlich erstellt der erste Weg einen temporären String (für Teilzeichenfolgen), der teurer als das Zeichenarray ist.
Hot Licks
1
Nicht hilfreich ohne unterstützende Daten
Nitsan Wakart
3

Eine sehr kurze und einfache statische Methode, um zu archivieren, was Sie wollen:

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

Wenn das, was Sie benötigen, sehr einfach ist (z. B. Java-Klassennamen, keine Gebietsschemas), können Sie auch die CaseFormat- Klasse in der Google Guava- Bibliothek verwenden.

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

Oder Sie können ein Konverterobjekt vorbereiten und wiederverwenden, was effizienter sein könnte.

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

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

Weitere Informationen zur Philosophie der Google Guava-Zeichenfolgenmanipulation finden Sie auf dieser Wiki-Seite .

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

Ich bin erst heute darauf gestoßen. Versucht, es selbst auf die fußgängerfreundlichste Weise zu tun. Das dauerte eine Zeile, obwohl es länger dauerte. Hier geht

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

Gibt:

Vor str = TaxoRanks

Nach str = taxoRanks

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

Ergebnis:

res4: String = hello
Vivek
quelle