web-dev-qa-db-fra.com

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

StringTokenizer? Convertir la String en char[] et effectuer une itération dessus? Autre chose?

259
Paul Wicks

J'utilise une boucle for pour parcourir la chaîne et j'utilise 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.

287
jjnguy

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. 

173
Dave Cheney

Notez que la plupart des autres techniques décrites ici s’opposent si vous utilisez des caractères situés en dehors de BMP (Unicode Plan multilingue de base ), c’est-à-dire des points de code qui se trouvent en dehors de u0000- gamme uFFFF. 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

87
sk.

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’entre eux avec charAt ()/après avoir converti en un tableau char/avec un CharacterIterator mille fois (en veillant bien sûr à faites 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 (c'est 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 sensiblement 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 abus du caractère '\ uFFFF' comme une "fin d'itération" un bidouillage 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");
22

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
   ...
}
19
Bruno De Fraine

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
17
Touko

Si vous devez parcourir les points de code d'une String (voir ceci answer ), une méthode plus courte/plus lisible consiste à utiliser la méthode 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’une IntStream, car il n’ya pas de CharStream).

12
Alex

Dans Java 8, nous pouvons 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 () retourne une IntStream comme mentionné dans doc :

Retourne un flux d'int entier prolongeant de zéro les valeurs de caractère à partir de this 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 une IntStream conformément à 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, par 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 passés au flux.

Quelle est la différence entre char et point de code? Comme mentionné dans this 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 une seule char 16 bits. Par conséquent, une valeur char no plus a une correspondance un-à-un avec l'unité sémantique fondamentale en Unicode. JDK 5 a été mis à jour pour prendre en charge le jeu de caractères plus large valeurs. Au lieu de changer la définition du type char, utilisez quelques-uns des 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 désigner le numéro qui représente un Unicode particulier caractère, y compris ceux 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 meet du flux si le flux a un ordre de rencontre défini. Donc, forEach ne garantit pas que la commande serait conservée. Vérifiez également cette question pour plus.

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

11
i_am_zero

Je n’utiliserais pas StringTokenizer car c’est l’une des classes de JDK héritées.

Le javadoc dit:

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

3
Alan

StringTokenizer est totalement inadapté à la tâche de diviser une chaîne en ses caractères individuels. Avec String#split(), vous pouvez le faire facilement en utilisant une expression régulière 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 y a est 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 aux délimiteurs:

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

Cependant, je mentionne uniquement ces options dans le but de les rejeter. Les deux techniques décomposent la chaîne d'origine en chaînes d'un caractère au lieu de primitives de caractères, et les deux méthodes impliquent une surcharge de temps 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 pas de temps système. 

0
Alan Moore

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

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 online Je reçois:

1 10349420
2 526130
3 484200
0

Sur Android x86 API 17, je reçois:

1 9122107
2 13486911
3 12700778
0
0
Enyby

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

Développer sur cette réponse et 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 . La documentation Java décrit é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 une chaîne. Notez que .toChars () renvoie un tableau de caractères: si vous avez affaire à des mères porteuses, 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))));
0
Hawkeye Parker

Voir Les tutoriels 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 la boucle for.

0
Eugene Yokota