Question

Encouragé par cette , et le fait que j'ai des milliards de chaîne à analyser , j'ai essayé de modifier mon code pour accepter StringTokenizer au lieu de String []

La seule chose qui reste entre moi et d'obtenir ce coup de pouce délicieux performance x2 est le fait que lorsque vous faites

"dog,,cat".split(",")
//output: ["dog","","cat"]

StringTokenizer("dog,,cat")
// nextToken() = "dog"
// nextToken() = "cat"

Comment puis-je obtenir des résultats similaires avec le StringTokenizer? Y at-il des moyens plus rapides pour ce faire?

Était-ce utile?

La solution

Vous ne tokenizing en fait sur des virgules? Si oui, j'écrire mon propre tokenizer - il pourrait bien finir par être encore plus efficace que l'objectif plus général StringTokenizer qui peut rechercher plusieurs jetons, et vous pouvez le faire se comporter comme vous le souhaitez. Pour un tel cas simple d'utilisation, il peut être une mise en œuvre simple.

S'il serait utile, vous pouvez même mettre en œuvre Iterable<String> et obtenir améliorée de boucle for support avec une forte frappe au lieu du support fourni par Enumeration StringTokenizer. Faites-moi savoir si vous voulez une aide codage d'une telle bête up -. Il ne devrait vraiment pas être trop dur

De plus, je vais essayer en cours d'exécution des tests de performance sur vos données réelles avant de sauter trop loin d'une solution existante. Avez-vous une idée de combien de temps d'exécution est en fait passé à String.split? Je sais que vous avez beaucoup de chaînes pour analyser, mais si vous faites quelque chose d'important avec eux par la suite, j'attendre à ce que d'être beaucoup plus important que la division.

Autres conseils

Après bricoler avec la classe StringTokenizer , je ne pouvais pas trouver un moyen de satisfaire aux exigences de retourner ["dog", "", "cat"].

En outre, la classe StringTokenizer reste seulement pour des raisons de compatibilité, et l'utilisation de String.split est encouaged. De l'API Spécification pour la StringTokenizer:

  

StringTokenizer est une classe héritée   qui est retenu pour la compatibilité   raisons bien que son utilisation   découragée dans le nouveau code. Il est   recommandé que tous ceux qui cherchent ce   fonctionnalité utiliser la méthode split   de String ou le java.util.regex   paquet à la place.

Étant donné que la question est la performance soi-disant pauvres du String.split méthode , nous devons trouver une solution de rechange.

Note: Je dis « performance soi-disant pauvres », car il est difficile de déterminer que tous les cas d'utilisation va entraîner la StringTokenizer étant supérieure à la méthode de String.split. En outre, dans de nombreux cas, à moins que le tokenization des chaînes sont en effet le goulot d'étranglement de l'application déterminée par le profilage proprement dit, je pense qu'il va finir par être une optimisation prématurée, si quoi que ce soit. Je serais enclin à dire écrire du code qui est significatif et facile à comprendre avant de se lancer sur l'optimisation.

Maintenant, des exigences actuelles, roulant probablement notre propre tokenizer ne serait pas trop difficile.

Roulez notre tokenzier!

Ce qui suit est un simple tokenizer je l'ai écrit. Je constate qu'il n'y a pas optimisations de vitesse, ni là des contrôles d'erreurs pour éviter d'aller après la fin de la chaîne - c'est une mise en œuvre rapide et sale:

class MyTokenizer implements Iterable<String>, Iterator<String> {
  String delim = ",";
  String s;
  int curIndex = 0;
  int nextIndex = 0;
  boolean nextIsLastToken = false;

  public MyTokenizer(String s, String delim) {
    this.s = s;
    this.delim = delim;
  }

  public Iterator<String> iterator() {
    return this;
  }

  public boolean hasNext() {
    nextIndex = s.indexOf(delim, curIndex);

    if (nextIsLastToken)
      return false;

    if (nextIndex == -1)
      nextIsLastToken = true;

    return true;
  }

  public String next() {
    if (nextIndex == -1)
      nextIndex = s.length();

    String token = s.substring(curIndex, nextIndex);
    curIndex = nextIndex + 1;

    return token;
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

Le MyTokenizer prendra String à tokenizer et un String comme séparateur, et utiliser la méthode String.indexOf pour effectuer la recherche de délimiteurs. Les jetons sont fabriqués par le procédé de String.substring.

Je pense qu'il pourrait y avoir des améliorations de performance en travaillant sur la chaîne au niveau char[] plutôt qu'au niveau de String. Mais je laisse cela comme un exercice au lecteur.

La classe implémente également Iterable Iterator afin de profiter de la for-each construction de la boucle qui a été introduite en Java 5. StringTokenizer est un Enumerator, et ne prend pas en charge la construction de for-each.

Est-il plus vite?

Pour savoir si cela est plus vite, j'ai écrit un programme pour comparer les vitesses dans les quatre méthodes suivantes:

  1. Utilisation de StringTokenizer.
  2. L'utilisation de la nouvelle MyTokenizer.
  3. Utilisation de String.split.
  4. Utilisation d'expression régulière précompilé par Pattern.compile .

Dans les quatre méthodes, le "dog,,cat" de chaîne a été séparé en jetons. Bien que le StringTokenizer est inclus dans la comparaison, il convient de noter qu'il ne retournera pas le résultat souhaité de ["dog", "", "cat].

Le tokenizing a été répétée pour un total de 1 million de fois pour donner suffisamment de temps pour prendre remarquer la différence dans les méthodes.

Le code utilisé pour le simple indice de référence était le suivant:

long st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
  StringTokenizer t = new StringTokenizer("dog,,cat", ",");
  while (t.hasMoreTokens()) {
    t.nextToken();
  }
}
System.out.println(System.currentTimeMillis() - st);

st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
  MyTokenizer mt = new MyTokenizer("dog,,cat", ",");
  for (String t : mt) {
  }
}
System.out.println(System.currentTimeMillis() - st);

st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
  String[] tokens = "dog,,cat".split(",");
  for (String t : tokens) {
  }
}
System.out.println(System.currentTimeMillis() - st);

st = System.currentTimeMillis();
Pattern p = Pattern.compile(",");
for (int i = 0; i < 1e6; i++) {
  String[] tokens = p.split("dog,,cat");
  for (String t : tokens) {
  }
}
System.out.println(System.currentTimeMillis() - st);

Les résultats

Les tests ont été run en utilisant Java SE 6 (build 1.6.0_12-b04), et les résultats étaient les suivants:

                   Run 1    Run 2    Run 3    Run 4    Run 5
                   -----    -----    -----    -----    -----
StringTokenizer      172      188      187      172      172
MyTokenizer          234      234      235      234      235
String.split        1172     1156     1171     1172     1156
Pattern.compile      906      891      891      907      906

Alors, comme on peut le voir à partir des essais limités et seulement cinq courses, le StringTokenizer a en fait sortir le plus rapide, mais le MyTokenizer est venu en tant près 2ème. Ensuite, String.split était le plus lent, et l'expression régulière précompilé était légèrement plus rapide que la méthode split.

Comme toute petite référence, il est sans doute pas très représentatif des conditions réelles, de sorte que les résultats doivent être pris avec un grain (ou un monticule) de sel.

Remarque: Après avoir fait quelques repères rapides, scanner se révèle être quatre fois plus lent que String.split. Par conséquent, ne pas utiliser le scanner.

(je quitte le poste pour enregistrer le fait que Scanner est une mauvaise idée dans ce cas (comprendre:. Ne me downvote pas suggérer Scanner, s'il vous plaît ...))

En supposant que vous utilisez Java 1.5 ou supérieur, essayez du scanner, qui implémente Iterator<String>, comme il arrive:

Scanner sc = new Scanner("dog,,cat");
sc.useDelimiter(",");
while (sc.hasNext()) {
    System.out.println(sc.next());
}

donne:

dog

cat

En fonction de ce type de chaînes que vous devez tokenizer, vous pouvez écrire votre propre séparateur basé sur String.indexOf () par exemple. Vous pouvez également créer une solution multi-core pour améliorer les performances encore plus loin, comme tokenization de chaînes est indépendant de l'autre. Les travaux sur des lots de 100 chaînes par cœur de -permet. Faites le String.split () ou watever autre.

Au lieu de StringTokenizer, vous pouvez essayer la classe StrTokenizer d'Apache Commons Lang, que je cite:

  

Cette classe peut diviser une chaîne en plusieurs chaînes plus petites. Il vise à faire un travail similaire à StringTokenizer, mais il offre beaucoup plus de contrôle et de flexibilité, y compris la mise en œuvre de l'interface ListIterator.

     

jetons vides peuvent être éliminés ou retournés comme nulle.

Cela ressemble à ce que vous avez besoin, je pense?

Vous pouvez faire quelque chose comme ça. Ce n'est pas parfait, mais il peut travailler pour vous.

public static List<String> find(String test, char c) {
    List<String> list = new Vector<String>();
    start;
    int i=0;
    while (i<=test.length()) {
        int start = i;
        while (i<test.length() && test.charAt(i)!=c) {
            i++;
        }
        list.add(test.substring(start, i));
        i++;
    }
    return list;
}

Si possible, vous pouvez ommettre la chose Liste et faire directement quelque chose à la sous-chaîne:

public static void split(String test, char c) {
    int i=0;
    while (i<=test.length()) {
        int start = i;
        while (i<test.length() && test.charAt(i)!=c) {
            i++;
        }
        String s = test.substring(start,i);
         // do something with the string here
        i++;
    }
}

Sur mon système la dernière méthode est plus rapide que la StringTokenizer solution, mais vous pouvez tester comment cela fonctionne pour vous. (Bien sûr, vous pouvez faire cette méthode un peu plus court par ommiting le {} du second en regard et bien sûr vous pouvez utiliser une boucle for au lieu de l'extérieur tout en boucle et y compris le dernier i ++ dans, mais je n » t faire ici parce que je considère que le mauvais style.

Eh bien, le plus rapide chose que vous pourriez faire serait de parcourir manuellement la chaîne, par exemple

List<String> split(String s) {
        List<String> out= new ArrayList<String>();
           int idx = 0;
           int next = 0;
        while ( (next = s.indexOf( ',', idx )) > -1 ) {
            out.add( s.substring( idx, next ) );
            idx = next + 1;
        }
        if ( idx < s.length() ) {
            out.add( s.substring( idx ) );
        }
               return out;
    }

(test informel) semble être quelque chose comme deux fois plus vite que Split. Cependant, il est un peu dangereux pour itérer cette façon, par exemple, il se brisera sur des virgules échappées, et si vous finissez par avoir besoin de traiter que, à un moment donné (parce que votre liste d'un milliard de chaînes a 3 échappé des virgules) au moment où vous « ai permis ce que vous allez probablement finir par perdre une partie de la prestation de vitesse.

En fin de compte, il est probablement ne vaut pas la peine.

Je recommande goyave de Google Splitter.
Je l'ai comparé avec coobird Test et a obtenu les résultats suivants:

  

StringTokenizer 104
  Google Goyave Splitter 142
  String.split 446
  regexp 299

Si votre entrée est structuré, vous pouvez jeter un oeil au compilateur JavaCC. Il génère une classe java lecture de votre entrée. Il ressemblerait à ceci:

TOKEN { <CAT: "cat"> , <DOG:"gog"> }

input: (cat() | dog())*


cat: <CAT>
   {
   animals.add(new Animal("Cat"));
   }

dog: <DOG>
   {
   animals.add(new Animal("Dog"));
   }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top