Najbardziej efektywny sposób, aby pierwszy znak w ciągu znaków był mały?


101

Jaki jest najskuteczniejszy sposób wpisania pierwszej Stringmałej litery?

Mogę wymyślić kilka sposobów, aby to zrobić:

Korzystanie charAt()zsubstring()

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

Lub za pomocą chartablicy

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

Jestem pewien, że istnieje wiele innych wspaniałych sposobów, aby to osiągnąć. Co polecasz?


Najlepszym sposobem byłaby zmiana wymagań, jeśli to możliwe. Zaakceptuj StringBuilder zamiast String i możesz go bezpośrednio modyfikować.
Mark Peters

Cóż, to nie jest odpowiedź, ponieważ jest poza Javą i opiera się na kodowaniu ASCII i wiedzy, że znak jest już alfabetyczny. To stary hack:c[0] |= ' ';
Mike Dunlavey


to inne pytanie
Andy

Odpowiedzi:


126

Przetestowałem obiecujące podejścia przy użyciu JMH . Pełny kod testu porównawczego .

Założenie podczas testów (aby uniknąć sprawdzania przypadków narożnych za każdym razem): wejściowa długość ciągu jest zawsze większa niż 1.

Wyniki

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

Wynik to liczba operacji na sekundę, im więcej, tym lepiej.

Testy

  1. test1 było pierwszym podejściem Andy'ego i Hllinka:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2było drugim podejściem Andy'ego. Introspector.decapitalize()Sugeruje to również Daniel, ale bez dwóch ifstwierdzeń. Pierwszy ifzostał usunięty z powodu założenia testowego. Drugi został usunięty, ponieważ naruszał poprawność (tzn. Wejście "HI"zwracało "HI"). To był prawie najszybszy.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
    
  3. test3była modyfikacją test2, ale zamiast tego Character.toLowerCase()dodałem 32, które działa poprawnie wtedy i tylko wtedy, gdy łańcuch jest w ASCII. To było najszybsze. c[0] |= ' 'z komentarza Mike'a dał taki sam występ.

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

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
    
  5. test5użył dwóch substring()połączeń.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6używa odbicia, aby zmienić char value[]bezpośrednio w ciągu. To było najwolniejsze.

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

Wnioski

Jeśli długość ciągu jest zawsze większa niż 0, użyj test2.

Jeśli nie, musimy sprawdzić narożniki:

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

Jeśli jesteś pewien, że twój tekst będzie zawsze w ASCII i szukasz ekstremalnej wydajności, ponieważ znalazłeś ten kod w wąskim gardle, użyj test3.


95

Natrafiłem na fajną alternatywę, jeśli nie chcesz korzystać z biblioteki innej firmy:

import java.beans.Introspector;

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

14
Z dokumentu dla tej metody: „Zwykle oznacza to zamianę pierwszego znaku z dużej na małą, ale w (nietypowym) przypadku specjalnym, gdy jest więcej niż jeden znak, a zarówno pierwszy, jak i drugi znak są duże, zostawiamy tylko to. "
Andy

1
Patrząc na źródło, gdy ta metoda obsługuje specjalny przypadek, który opisałem w poprzednim komentarzu, po prostu używa tablicy char, o której wspomniałem w moim pytaniu.
Andy

2
Dokładnie to, czego potrzebowałem. Introspector.decapitalize („ABC”) nadal będzie ABC. WordUtils.uncapitalize („ABC”) generuje „aBC”. Po prostu dzielę się, że to pierwsze jest sposobem, w jaki wiosna wykonuje automatyczne nazewnictwo ziaren, więc jeśli chcesz pobrać nazwę ziarna ABCService, nie jest to aBCService, ale nadal ABCService.
wieśniak

21

Jeśli chodzi o manipulację ciągami znaków, spójrz na Jakarta Commons Lang StringUtils .


8
Mówiąc dokładniej, metoda uncapitalize (java.lang.String) Using StringUtils ma dodatkową zaletę polegającą na tym, że nie trzeba martwić się o wyjątki NullPointerExceptions w kodzie.
heksium

3
Niekoniecznie najbardziej wydajne, ale chyba najbardziej przejrzyste, co się liczy.
David Gelhar

2
Zależy od tego, jakie zasoby robisz bardziej wydajnie - CPU lub czas programisty :)
Dan Gravell

15

Jeśli chcesz korzystać z Apache Commons, możesz wykonać następujące czynności:

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

Wynik: someString


3
To ładne i czyste rozwiązanie, ale teraz jest to przestarzałe, powinniśmy używać compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
zwykłych

10

Pomimo podejścia zorientowanego na znaki, sugerowałbym rozwiązanie zorientowane na String. String.toLowerCase jest specyficzne dla lokalizacji, więc wziąłbym ten problem pod uwagę. String.toLowerCasejest preferowanie małych liter zgodnie z Character.toLowerCase . Również rozwiązanie zorientowane na znaki nie jest w pełni kompatybilne z Unicode, ponieważ Character.toLowerCase nie obsługuje dodatkowych znaków.

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

AKTUALIZACJA: Jako przykład, jak ważne są ustawienia regionalne, użyjmy małych liter Ipo turecku i niemiecku:

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

zwróci dwa różne wyniki:

ja

ja


7

Ciągi w Javie są niezmienne, więc w obu przypadkach zostanie utworzony nowy ciąg.

Twój pierwszy przykład będzie prawdopodobnie nieco bardziej wydajny, ponieważ wystarczy utworzyć nowy ciąg, a nie tymczasową tablicę znaków.


1
Właściwie pierwszy sposób tworzy tymczasowy ciąg (dla podłańcucha), który jest droższy niż tablica znaków.
Hot Licks

1
Nieprzydatne bez wsparcia danych
Nitsan Wakart

3

Bardzo krótka i prosta statyczna metoda archiwizacji tego, co chcesz:

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

2

Jeśli to, czego potrzebujesz, jest bardzo proste (np. Nazwy klas java, bez lokalizacji), możesz również użyć klasy CaseFormat w bibliotece Google Guava .

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

Możesz też przygotować i ponownie użyć konwertera, co może być bardziej wydajne.

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

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

Aby lepiej zrozumieć filozofię manipulacji ciągiem Google Guava, sprawdź tę stronę wiki .


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

1

Przeszedłem przez to dopiero dzisiaj. Próbowałem to zrobić samemu w najbardziej pieszy sposób. To zajęło jedną linijkę, choć długą. Tutaj idzie

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

Daje:

Przed str = TaxoRanks

Po str = taxoRanks


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

Wynik:

res4: String = hello
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.