Quel est le moyen le plus facile / le meilleur / le plus correct pour parcourir les caractères d'une chaîne en Java?

StackOverflow https://stackoverflow.com/questions/196830

Question

StringTokenizer ? Convertir la String en char [] et effectuer une itération dessus? Quelque chose d'autre?

Était-ce utile?

La solution

J'utilise une boucle for pour itérer la chaîne et charAt () pour que chaque caractère l'examine. Puisque la chaîne est implémentée avec un tableau, la méthode charAt () est une opération à temps constant.

String s = "...stuff...";

for (int i = 0; i < s.length(); i++){
    char c = s.charAt(i);        
    //Process char
}

C'est ce que je ferais. Cela me semble le plus facile.

En ce qui concerne l’exactitude, je ne crois pas que cela existe ici. Tout est basé sur votre style personnel.

Autres conseils

Deux options

for(int i = 0, n = s.length() ; i < n ; i++) { 
    char c = s.charAt(i); 
}

ou

for(char c : s.toCharArray()) {
    // process c
}

Le premier est probablement plus rapide, alors que 2nd est probablement plus lisible.

Notez que la plupart des autres techniques décrites ici s’effondrent s’il s’agit de caractères extérieurs au BMP (Unicode Plan multilingue de base ), c’est-à-dire les points de code qui ne font pas partie du code u0000 Gamme -FFFF. Cela ne se produira que rarement, car les points de code extérieurs sont principalement affectés à des langues mortes. Mais il y a quelques caractères utiles en dehors de cela, par exemple des points de code utilisés pour la notation mathématique et d'autres pour coder les noms propres en chinois.

Dans ce cas, votre code sera:

String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
  int curChar = str.codePointAt(offset);
  offset += Character.charCount(curChar);
  // do something with curChar
}

La méthode Character.charCount (int) nécessite Java 5 +.

Source: http://mindprod.com/jgloss/codepoint.html

Je conviens que StringTokenizer est excessif ici. En fait, j'ai essayé les suggestions ci-dessus et pris le temps.

Mon test était assez simple: créer un StringBuilder avec environ un million de caractères, le convertir en chaîne, et parcourir chacun d'eux avec charAt () / après la conversion en un tableau char / avec un CharacterIterator mille fois (bien sûr). assurez-vous de faire quelque chose sur la chaîne pour que le compilateur ne puisse pas optimiser toute la boucle :-)).

Le résultat sur mon Powerbook à 2,6 GHz (un Mac :-)) et JDK 1.5:

  • Test 1: charAt + String - > 3138msec
  • Test 2: chaîne convertie en tableau - > 9568msec
  • Test 3: Chargeur StringBuilder - > 3536msec
  • Test 4: CharacterIterator et String - > 12151msec

Les résultats étant très différents, le moyen le plus simple semble également être le plus rapide. Fait intéressant, charAt () d'un StringBuilder semble être légèrement plus lent que celui de String.

BTW, je suggère de ne pas utiliser CharacterIterator, car je considère son utilisation abusive du caractère '\ uFFFF' comme "fin d'itération". un bidule vraiment horrible. Dans les grands projets, il y a toujours deux types qui utilisent le même type de bidouillage à des fins différentes et le code plante de manière très mystérieuse.

Voici l'un des tests:

    int count = 1000;
    ...

    System.out.println("Test 1: charAt + String");
    long t = System.currentTimeMillis();
    int sum=0;
    for (int i=0; i<count; i++) {
        int len = str.length();
        for (int j=0; j<len; j++) {
            if (str.charAt(j) == 'b')
                sum = sum + 1;
        }
    }
    t = System.currentTimeMillis()-t;
    System.out.println("result: "+ sum + " after " + t + "msec");

Il existe des classes dédiées à cela:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}

Si vous avez Guava sur votre chemin de classe, voici une alternative très lisible. . Guava a même une implémentation List assez judicieuse dans ce cas, donc cela ne devrait pas être inefficace.

for(char c : Lists.charactersOf(yourString)) {
    // Do whatever you want     
}

UPDATE: Comme l'a noté @Alex, avec Java 8, il existe également CharSequence # chars à utiliser. Même le type est IntStream, il peut donc être mappé à des caractères tels que:

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want

Dans Java 8 , vous pouvez le résoudre comme suit:

String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));

La méthode chars () renvoie un IntStream comme indiqué dans doc :

  

Renvoie un flux d'int-extension à zéro des valeurs char de cette   séquence. Tout caractère mappé sur un point de code de substitution est passé   à travers non interprété. Si la séquence est mutée alors que le flux est   en cours de lecture, le résultat est indéfini.

La méthode codePoints () renvoie également un IntStream conforme à la documentation:

  

Renvoie un flux de valeurs de points de code à partir de cette séquence. Tout   Les paires de substitution rencontrées dans la séquence sont combinées comme si   Character.toCodePoint et le résultat est transmis au flux. Tout   autres unités de code, y compris les caractères BMP ordinaires, non appariées   les substituts et les unités de code non définies sont étendus à zéro aux valeurs int   qui sont ensuite transmis au flux.

En quoi le caractère et le point de code sont-ils différents? Comme indiqué dans cet article :

  

Unicode 3.1 a ajouté des caractères supplémentaires, ce qui porte le nombre total   de caractères à plus de 216 caractères qui peuvent être   distingué par un seul char 16 bits. Par conséquent, une valeur char non   a plus une correspondance un-à-un avec l'unité sémantique fondamentale   Unicode. JDK 5 a été mis à jour pour prendre en charge le plus grand ensemble de caractères.   valeurs. Au lieu de changer la définition du type char , certains de   les nouveaux caractères supplémentaires sont représentés par une paire de substitution   de deux valeurs char . Pour réduire la confusion des noms, un point de code sera   utilisé pour faire référence au numéro qui représente un Unicode particulier   caractère, y compris les caractères supplémentaires.

Enfin, pourquoi forEachOrdered et non forEach ?

Le comportement de forEach est explicitement non déterministe, dans la mesure où forEachOrdered exécute une action pour chaque élément de ce flux, dans l'ordre de rencontre du flux si le flux a un ordre de rencontre défini. Donc, forEach ne garantit pas que la commande sera conservée. Consultez également cette question pour plus d'informations.

Pour différence entre un caractère, un point de code, un glyphe et un graphème , cochez question .

Si vous devez parcourir les points de code d'une String (consultez cette answer . ) une méthode plus courte / plus lisible consiste à utiliser CharSequence # codePoints ajoutée à Java 8:

for(int c : string.codePoints().toArray()){
    ...
}

ou en utilisant le flux directement au lieu d'une boucle for:

string.codePoints().forEach(c -> ...);

Il existe également CharSequence # chars si vous souhaitez un flux de caractères (bien qu'il s'agisse d'un IntStream , car il n'y a pas de CharStream ).

Je n'utiliserais pas StringTokenizer car il s'agit d'une des classes du kit JDK héritées du passé.

Le javadoc dit:

  

StringTokenizer est une classe héritée qui   est retenu pour des raisons de compatibilité   bien que son utilisation soit découragée dans les nouveaux   code. Il est recommandé à quiconque   recherchant cette fonctionnalité utiliser le   méthode split de String ou le    package java.util.regex à la place.

Si vous avez besoin de performances, vous devez tester sur votre environnement. Pas d'autre moyen.

Voici un exemple de code:

int tmp = 0;
String s = new String(new byte[64*1024]);
{
    long st = System.nanoTime();
    for(int i = 0, n = s.length(); i < n; i++) {
        tmp += s.charAt(i);
    }
    st = System.nanoTime() - st;
    System.out.println("1 " + st);
}

{
    long st = System.nanoTime();
    char[] ch = s.toCharArray();
    for(int i = 0, n = ch.length; i < n; i++) {
        tmp += ch[i];
    }
    st = System.nanoTime() - st;
    System.out.println("2 " + st);
}
{
    long st = System.nanoTime();
    for(char c : s.toCharArray()) {
        tmp += c;
    }
    st = System.nanoTime() - st;
    System.out.println("3 " + st);
}
System.out.println("" + tmp);

Sur Java en ligne , je reçois:

1 10349420
2 526130
3 484200
0

Sur l'API Android x86 17, je reçois:

1 9122107
2 13486911
3 12700778
0

Voir Didacticiels Java: Chaînes .

public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        } 

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = tempCharArray[len - 1 - j];
        }

        String reversePalindrome =  new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Mettez la longueur dans int len ?? et utilisez pour la boucle .

StringTokenizer est totalement inadapté à la tâche de diviser une chaîne en caractères individuels. Avec String # split () , vous pouvez le faire facilement en utilisant une expression rationnelle qui ne correspond à rien, par exemple:

.
String[] theChars = str.split("|");

Mais StringTokenizer n'utilise pas de regex, et vous ne pouvez spécifier aucune chaîne de délimiteur qui corresponde au rien entre les caractères. Il existe un un joli petit hack que vous pouvez utiliser pour accomplir la même chose: utilisez la chaîne elle-même comme chaîne de délimiteur (en faisant de chaque caractère un délimiteur) et faites-la retourner les délimiteurs:

StringTokenizer st = new StringTokenizer(str, str, true);

Cependant, je ne mentionne ces options que dans le but de les rejeter. Les deux techniques décomposent la chaîne d'origine en chaînes à un caractère au lieu de primitives de caractères et impliquent une surcharge de travail considérable sous la forme de création d'objet et de manipulation de chaîne. Comparez cela à l'appel de charAt () dans une boucle for, ce qui n'entraîne pratiquement aucun temps système.

Précisions sur cette réponse et sur cette réponse .

Les réponses ci-dessus soulignent le problème posé par de nombreuses solutions sans itérer par valeur de point de code. Elles auraient des problèmes avec les caractères de substitution . Les documents java décrivent également le problème ici . (voir "Représentations de caractères Unicode"). Quoi qu'il en soit, voici un code qui utilise des caractères de substitution réels du jeu Unicode supplémentaire et les convertit back en String. Notez que .toChars () renvoie un tableau de caractères: si vous avez des substituts, vous aurez nécessairement deux caractères. Ce code devrait fonctionner pour tous les caractères Unicode.

    String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));

Cet exemple de code vous aidera!

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Solution {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("a", 10);
        map.put("b", 30);
        map.put("c", 50);
        map.put("d", 40);
        map.put("e", 20);
        System.out.println(map);

        Map sortedMap = sortByValue(map);
        System.out.println(sortedMap);
    }

    public static Map sortByValue(Map unsortedMap) {
        Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
        sortedMap.putAll(unsortedMap);
        return sortedMap;
    }

}

class ValueComparator implements Comparator {
    Map map;

    public ValueComparator(Map map) {
        this.map = map;
    }

    public int compare(Object keyA, Object keyB) {
        Comparable valueA = (Comparable) map.get(keyA);
        Comparable valueB = (Comparable) map.get(keyB);
        return valueB.compareTo(valueA);
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top