Scanner vs StringTokenizer vs String.Split

155

Je viens d'apprendre la classe Scanner de Java et maintenant je me demande comment elle se compare / concurrence avec StringTokenizer et String.Split. Je sais que StringTokenizer et String.Split ne fonctionnent que sur des chaînes, alors pourquoi voudrais-je utiliser le scanner pour une chaîne? Scanner est-il uniquement destiné à être un guichet unique pour le fractionnement?

Dave
la source

Réponses:

240

Ce sont essentiellement des chevaux pour les cours.

  • Scannerest conçu pour les cas où vous devez analyser une chaîne, extraire des données de différents types. C'est très flexible, mais sans doute ne vous donne pas l'API la plus simple pour simplement obtenir un tableau de chaînes délimitées par une expression particulière.
  • String.split()et Pattern.split()vous donner une syntaxe simple pour faire ce dernier, mais c'est essentiellement tout ce qu'ils font. Si vous souhaitez analyser les chaînes résultantes ou modifier le délimiteur à mi-chemin en fonction d'un jeton particulier, ils ne vous aideront pas.
  • StringTokenizerest encore plus restrictif que String.split(), et aussi un peu plus compliqué à utiliser. Il est essentiellement conçu pour extraire des jetons délimités par des sous-chaînes fixes. En raison de cette restriction, c'est environ deux fois plus rapide que String.split(). (Voir ma comparaison de String.split()etStringTokenizer .) Il est également antérieur à l'API des expressions régulières, dont String.split()fait partie.

Vous remarquerez d'après mes timings qui String.split()peuvent encore symboliser des milliers de chaînes en quelques millisecondes sur une machine typique. De plus, il a l'avantage de StringTokenizervous donner la sortie sous forme de tableau de chaînes, ce qui est généralement ce que vous voulez. Utiliser un Enumeration, tel que fourni par StringTokenizer, est la plupart du temps trop «syntaxiquement difficile». De ce point de vue, StringTokenizerc'est un peu un gaspillage d'espace de nos jours, et vous pouvez aussi bien l'utiliser String.split().

Neil Coffey
la source
8
Il serait également intéressant de voir les résultats de Scanner sur les mêmes tests que vous avez exécutés sur String.Split et StringTokenizer.
Dave
2
M'a donné une réponse à une autre question: "pourquoi l'utilisation de StringTokenizer est-elle déconseillée, comme indiqué dans les notes de l'API Java?". D'après ce texte, il semble que la réponse serait "parce que String.split () est assez rapide".
Jambes
1
Donc, StringTokenizer est-il à peu près obsolète maintenant?
Steve the Maker
quoi utiliser à la place? Scanner?
Adrian le
4
Je me rends compte que c'est une réponse à une vieille question, mais si je dois diviser un énorme flux de texte en jetons à la volée, n'est-ce pas StringTokenizertoujours mon meilleur pari car String.split()je manquerai simplement de mémoire?
Sergei Tachenov le
57

Commençons par éliminer StringTokenizer. Il vieillit et ne prend même pas en charge les expressions régulières. Sa documentation indique:

StringTokenizerest une classe héritée qui est conservée pour des raisons de compatibilité bien que son utilisation soit déconseillée dans le nouveau code. Il est recommandé à toute personne recherchant cette fonctionnalité d'utiliser la splitméthode de Stringou le java.util.regexpackage à la place.

Alors jetons-le tout de suite. Cela laisse split()et Scanner. Quelle est la différence entre eux?

D'une part, split()renvoie simplement un tableau, ce qui facilite l'utilisation d'une boucle foreach:

for (String token : input.split("\\s+") { ... }

Scanner est construit plus comme un flux:

while (myScanner.hasNext()) {
    String token = myScanner.next();
    ...
}

ou

while (myScanner.hasNextDouble()) {
    double token = myScanner.nextDouble();
    ...
}

(Il a une API assez volumineuse , alors ne pensez pas qu'elle est toujours limitée à des choses aussi simples.)

Cette interface de style flux peut être utile pour analyser des fichiers texte simples ou des entrées de console, lorsque vous n'avez pas (ou ne pouvez pas obtenir) toutes les entrées avant de commencer l'analyse.

Personnellement, le seul moment où je me souviens avoir utilisé Scannerest pour les projets scolaires, lorsque je devais obtenir les entrées de l'utilisateur à partir de la ligne de commande. Cela facilite ce genre d'opération. Mais si j'ai un Stringque je veux séparer, c'est presque une évidence split().

Michael Myers
la source
20
StringTokenizer est 2x plus rapide que String.split (). Si vous n'avez pas BESOIN d'utiliser des expressions régulières, PAS!
Alex Worden
Je viens Scannerde détecter les caractères de nouvelle ligne dans une donnée String. Puisque les caractères de nouvelle ligne peuvent varier d'une plate-forme à l'autre (regardez Patternle javadoc de 's!) Et que la chaîne d'entrée n'est PAS garantie de se conformer à System.lineSeparator(), je trouve Scannerplus approprié car il sait déjà quels nouveaux caractères de ligne rechercher lors de l'appel nextLine(). Car String.splitje devrai alimenter le bon motif regex pour détecter les séparateurs de ligne, que je ne trouve pas stockés dans un emplacement standard (le mieux que je puisse faire est de le copier à partir de la Scannersource de la classe).
ADTC
9

StringTokenizer était toujours là. C'est le plus rapide de tous, mais l'idiome de type énumération peut ne pas sembler aussi élégant que les autres.

split a vu le jour sur JDK 1.4. Plus lent que tokenizer mais plus facile à utiliser, car il peut être appelé à partir de la classe String.

Scanner est venu pour être sur JDK 1.5. C'est le plus flexible et comble une lacune de longue date sur l'API Java pour prendre en charge un équivalent de la célèbre famille de fonctions scanf Cs.

H Marcelo Morales
la source
6

Si vous avez un objet String que vous souhaitez tokenize, privilégiez l'utilisation de la méthode split de String par rapport à StringTokenizer. Si vous analysez des données texte à partir d'une source extérieure à votre programme, comme à partir d'un fichier, ou de l'utilisateur, c'est là qu'un scanner est utile.

Bill le lézard
la source
5
Juste comme ça, pas de justification, pas de raison?
jan.supol
6

Le fractionnement est lent, mais pas aussi lent que le scanner. StringTokenizer est plus rapide que split. Cependant, j'ai trouvé que je pouvais obtenir le double de la vitesse, en échangeant une certaine flexibilité, pour obtenir un gain de vitesse, ce que j'ai fait chez JFastParser https://github.com/hughperkins/jfastparser

Test sur une chaîne contenant un million de doubles:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
Hugh Perkins
la source
Certains Javadoc auraient été bien, et que faire si vous voulez analyser autre chose que des données numériques?
NickJ
Eh bien, il est conçu pour la vitesse, pas la beauté. C'est assez simple, juste quelques lignes, vous pouvez donc ajouter quelques options supplémentaires pour l'analyse de texte si vous le souhaitez.
Hugh Perkins
4

String.split semble être beaucoup plus lent que StringTokenizer. Le seul avantage de la division est que vous obtenez un tableau de jetons. Vous pouvez également utiliser toutes les expressions régulières dans split. org.apache.commons.lang.StringUtils a une méthode de fractionnement qui fonctionne beaucoup plus rapidement que l'un des deux à savoir. StringTokenizer ou String.split. Mais l'utilisation du processeur pour les trois est presque la même. Nous avons donc également besoin d'une méthode moins gourmande en CPU, que je ne suis toujours pas en mesure de trouver.

Manish
la source
3
Cette réponse est un peu absurde. Vous dites que vous recherchez quelque chose de plus rapide mais "moins gourmand en CPU". Tout programme est exécuté par la CPU. Si un programme n'utilise pas votre CPU à 100%, alors il doit attendre autre chose, comme les E / S. Cela ne devrait jamais être un problème lors de la discussion de la tokenisation des chaînes, sauf si vous effectuez un accès direct au disque (ce que nous ne faisons notamment pas ici).
Jolta le
4

J'ai récemment fait des expériences sur les mauvaises performances de String.split () dans des situations très sensibles aux performances. Cela peut vous être utile.

http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr

L'essentiel est que String.split () compile un modèle d'expression régulière à chaque fois et peut donc ralentir votre programme, par rapport à si vous utilisez un objet Pattern précompilé et que vous l'utilisez directement pour opérer sur une chaîne.

pdeva
la source
4
En fait, String.split () ne compile pas toujours le modèle. Regardez la source si 1.7 java, vous verrez qu'il y a une vérification si le motif est un caractère unique et non un caractère échappé, il divisera la chaîne sans expression régulière, donc cela devrait être assez rapide.
Krzysztof Krasoń
1

Pour les scénarios par défaut, je suggérerais également Pattern.split () mais si vous avez besoin de performances maximales (en particulier sur Android, toutes les solutions que j'ai testées sont assez lentes) et que vous n'avez besoin de diviser que par un seul caractère, j'utilise maintenant ma propre méthode:

public static ArrayList<String> splitBySingleChar(final char[] s,
        final char splitChar) {
    final ArrayList<String> result = new ArrayList<String>();
    final int length = s.length;
    int offset = 0;
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (s[i] == splitChar) {
            if (count > 0) {
                result.add(new String(s, offset, count));
            }
            offset = i + 1;
            count = 0;
        } else {
            count++;
        }
    }
    if (count > 0) {
        result.add(new String(s, offset, count));
    }
    return result;
}

Utilisez "abc" .toCharArray () pour obtenir le tableau de caractères d'une chaîne. Par exemple:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
Simon
la source
1

Une différence importante est que String.split () et Scanner peuvent produire des chaînes vides, mais StringTokenizer ne le fait jamais.

Par exemple:

String str = "ab cd  ef";

StringTokenizer st = new StringTokenizer(str, " ");
for (int i = 0; st.hasMoreTokens(); i++) System.out.println("#" + i + ": " + st.nextToken());

String[] split = str.split(" ");
for (int i = 0; i < split.length; i++) System.out.println("#" + i + ": " + split[i]);

Scanner sc = new Scanner(str).useDelimiter(" ");
for (int i = 0; sc.hasNext(); i++) System.out.println("#" + i + ": " + sc.next());

Production:

//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2: 
#3: ef
//Scanner
#0: ab
#1: cd
#2: 
#3: ef

En effet, le délimiteur de String.split () et Scanner.useDelimiter () n'est pas simplement une chaîne, mais une expression régulière. Nous pouvons remplacer le délimiteur "" par "+" dans l'exemple ci-dessus pour les faire se comporter comme StringTokenizer.

Jean29
la source
-5

String.split () fonctionne très bien mais a ses propres limites, comme si vous vouliez diviser une chaîne comme indiqué ci-dessous en fonction du symbole de tube simple ou double (|), cela ne fonctionne pas. Dans cette situation, vous pouvez utiliser StringTokenizer.

ABC | IJK

Mujahid shaik
la source
12
En fait, vous pouvez diviser votre exemple avec juste "ABC | IJK" .split ("\\ |");
Tomo
"ABC || DEF ||" .split ("\\ |") ne fonctionne pas vraiment car il ignorera les deux valeurs vides de fin, ce qui rend l'analyse plus compliquée qu'elle ne devrait l'être.
Armand