Le moyen le plus efficace de rendre le premier caractère d'une chaîne en minuscule?


101

Quelle est la manière la plus efficace de créer le premier caractère d'une Stringminuscule?

Je peux penser à plusieurs façons de procéder:

Utilisation charAt()avecsubstring()

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

Ou en utilisant un chartableau

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

Je suis sûr qu'il existe de nombreux autres moyens pour y parvenir. Que recommandez-vous?


La meilleure façon serait de changer vos exigences si possible. Acceptez un StringBuilder au lieu d'une String et vous pouvez le modifier directement.
Mark Peters

Eh bien, ce n'est pas une réponse car il est en dehors de Java, et repose sur l'encodage ASCII et sur le fait de savoir que le caractère est déjà alphabétique. C'est un hack de vieux:c[0] |= ' ';
Mike Dunlavey


c'est une question différente
Andy

Réponses:


126

J'ai testé les approches prometteuses en utilisant JMH . Code de référence complet .

Hypothèse lors des tests (pour éviter de vérifier les cas d'angle à chaque fois): la longueur de la chaîne d'entrée est toujours supérieure à 1.

Résultats

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

Les scores sont des opérations par seconde, plus il y en a, mieux c'est.

Des tests

  1. test1 était d'abord l'approche d'Andy et Hllink:

    string = Character.toLowerCase(string.charAt(0)) + string.substring(1);
  2. test2était la deuxième approche d'Andy. Il est également Introspector.decapitalize()suggéré par Daniel, mais sans deux ifdéclarations. First a ifété supprimé en raison de l'hypothèse de test. Le second a été supprimé, car il enfreignait l'exactitude (c'est-à-dire que l'entrée "HI"reviendrait "HI"). C'était presque le plus rapide.

    char c[] = string.toCharArray();
    c[0] = Character.toLowerCase(c[0]);
    string = new String(c);
    
  3. test3était une modification de test2, mais au lieu de Character.toLowerCase(), j'ajoutais 32, qui fonctionne correctement si et seulement si la chaîne est en ASCII. C'était le plus rapide. c[0] |= ' 'du commentaire de Mike a donné la même performance.

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

    StringBuilder sb = new StringBuilder(string);
    sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    string = sb.toString();
    
  5. test5utilisé deux substring()appels.

    string = string.substring(0, 1).toLowerCase() + string.substring(1);
  6. test6utilise la réflexion pour changer char value[]directement dans String. C'était le plus lent.

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

Conclusions

Si la longueur de la chaîne est toujours supérieure à 0, utilisez test2.

Sinon, nous devons vérifier les cas d'angle:

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 vous êtes sûr que votre texte sera toujours en ASCII et que vous recherchez des performances extrêmes car vous avez trouvé ce code dans le goulot d'étranglement, utilisez test3.


95

Je suis tombé sur une bonne alternative si vous ne souhaitez pas utiliser une bibliothèque tierce:

import java.beans.Introspector;

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

14
Extrait de la documentation de cette méthode: "Cela signifie normalement convertir le premier caractère des majuscules aux minuscules, mais dans le cas spécial (inhabituel) lorsqu'il y a plus d'un caractère et que les premier et deuxième caractères sont en majuscules, nous laissons seul. "
Andy

1
De plus, en regardant la source, une fois que cette méthode gère le cas particulier que j'ai décrit dans le commentaire précédent, elle utilise simplement le tableau char comme je l'avais mentionné dans ma question.
Andy

2
Exactement ce dont j'avais besoin. Introspector.decapitalize ("ABC") sera toujours ABC. WordUtils.uncapitalize ("ABC") produit "aBC". Le simple fait de partager que le premier est la façon dont Spring fait son nom automatique des beans, donc si vous avez besoin de récupérer par nom de bean le ABCService, ce n'est pas aBCService, mais ABCService quand même.
villageois

21

Quand il s'agit de manipulation de chaînes, jetez un œil à Jakarta Commons Lang StringUtils .


8
Plus précisément, la méthode uncapitalize (java.lang.String) Using StringUtils a l'avantage supplémentaire de ne pas avoir à se soucier des NullPointerExceptions dans votre code.
hexium

3
Pas forcément le plus efficace, mais peut-être le plus clair, ce qui compte pour beaucoup.
David Gelhar

2
Dépend de la ressource que vous rendez plus efficace - temps processeur ou programmeur :)
Dan Gravell

15

Si vous souhaitez utiliser Apache Commons, vous pouvez effectuer les opérations suivantes:

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

Résultat: someString


3
C'est une solution agréable et propre, mais c'est obsolète maintenant, nous devrions utiliser les textes communs:compile group: 'org.apache.commons', name: 'commons-text', version: '1.2'
dk7

10

Malgré une approche orientée char, je suggérerais une solution orientée String. String.toLowerCase est spécifique aux paramètres régionaux, je prendrais donc en compte ce problème. String.toLowerCaseest de préférer les minuscules selon Character.toLowerCase . De plus, une solution orientée char n'est pas entièrement compatible avec unicode, car Character.toLowerCase ne peut pas gérer les caractères supplémentaires.

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

MISE À JOUR: À titre d'exemple à quel point le paramètre régional est important, laissez-nous en minuscules Ien turc et en allemand:

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

produira deux résultats différents:

je

je


7

Les chaînes en Java sont immuables, donc dans les deux cas, une nouvelle chaîne sera créée.

Votre premier exemple sera probablement un peu plus efficace car il suffit de créer une nouvelle chaîne et non un tableau de caractères temporaire.


1
En fait, la première méthode crée une chaîne temporaire (pour la sous-chaîne), qui est plus chère que le tableau de caractères.
Hot Licks

1
Inutile sans données de support
Nitsan Wakart

3

Une méthode statique très courte et simple pour archiver ce que vous voulez:

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

2

Si ce dont vous avez besoin est très simple (par exemple, noms de classe java, pas de paramètres régionaux), vous pouvez également utiliser la classe CaseFormat dans la bibliothèque Google Guava .

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

Ou vous pouvez préparer et réutiliser un objet convertisseur, ce qui pourrait être plus efficace.

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

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

Pour mieux comprendre la philosophie de la manipulation des chaînes de Google Guava, consultez cette page wiki .


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

1

Je n'ai rencontré cela qu'aujourd'hui. J'ai essayé de le faire moi-même de la manière la plus piétonne. Cela a pris une ligne, bien que longue. Voici

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

Donne:

Avant str = TaxoRanks

Après str = taxoRanks


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

Résultat:

res4: String = hello
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.