5

これに勇気づけられ、解析する文字列が数十億あるという事実から、 String[]の代わりにStringTokenizerを受け入れるようにコードを変更しようとしました。

私とそのおいしいx2パフォーマンスの向上を得るために残された唯一のことは、あなたがしているときに

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

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

StringTokenizerで同様の結果を得るにはどうすればよいですか?これを行うためのより速い方法はありますか?

4

9 に答える 9

12

実際にカンマでトークン化するだけですか?もしそうなら、私は自分のトークナイザーを書くでしょう-それは複数のトークンを探すことができるより汎用的なStringTokenizerよりもさらに効率的になるかもしれません、そしてあなたはそれを好きなように振る舞わせることができます。このような単純なユースケースの場合、それは単純な実装である可能性があります。

便利な場合は、によって提供されるサポートIterable<String>の代わりに、強い型付けを使用して拡張forループサポートを実装して取得することもできます。そのような獣をコーディングするのに助けが必要な場合は私に知らせてください-それは本当に難しいことではありません。EnumerationStringTokenizer

さらに、既存のソリューションから大きく離れる前に、実際のデータに対してパフォーマンステストを実行してみます。実行時間のどれだけが実際に費やされているかわかりますString.splitか?解析する文字列がたくさんあることは知っていますが、後でそれらを使用して重要なことを行う場合は、分割よりもはるかに重要であると思います。

于 2009-06-12T13:11:11.457 に答える
10

クラスをいじくり回した後StringTokenizer、私は戻るための要件を満たす方法を見つけることができませんでした["dog", "", "cat"]

さらに、StringTokenizerクラスは互換性の理由でのみ残されており、の使用String.splitが推奨されています。のAPI仕様からStringTokenizer

StringTokenizerは、互換性の理由で保持されているレガシークラスですが、新しいコードでは使用しないでください。この機能をお探しの方は、代わりにまたは パッケージのsplitメソッドを使用することをお勧めします。Stringjava.util.regex

問題はおそらくメソッドのパフォーマンスの低下であるString.splitため、代替手段を見つける必要があります。

StringTokenizer注:すべてのユースケースがこの方法よりも優れていると判断するのは難しいため、「パフォーマンスが低いと思われる」と言っていますString.split。さらに、多くの場合、文字列のトークン化が実際に適切なプロファイリングによって決定されるアプリケーションのボトルネックでない限り、どちらかといえば時期尚早の最適化になると思います。最適化に取り掛かる前に、意味があり理解しやすいコードを書くと言いたくなります。

現在の要件からすると、おそらく独自のトークナイザーをローリングすることはそれほど難しくありません。

私たち自身のtokenzierを転がしてください!

以下は私が書いた単純なトークナイザーです。速度の最適化はなく、文字列の終わりを超えないようにするためのエラーチェックもありません。これは手っ取り早い実装です。

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

MyTokenizerStringをトークン化しStringて区切り文字として使用し、このString.indexOfメソッドを使用して区切り文字の検索を実行します。トークンはString.substringメソッドによって生成されます。

char[]レベルではなくレベルで文字列を操作することで、パフォーマンスが向上する可能性があると思いStringます。しかし、それは読者の練習問題として残しておきます。

このクラスは、Java 5で導入されたループ構造を実装Iterableし、それIteratorを利用するために、であり、構造をサポートしていません。for-eachStringTokenizerEnumeratorfor-each

もっと速いですか?

これがもっと速いかどうかを調べるために、私は次の4つの方法で速度を比較するプログラムを作成しました。

  1. の使用StringTokenizer
  2. 新しいの使用MyTokenizer
  3. の使用String.split
  4. によるプリコンパイルされた正規表現の使用Pattern.compile

4つの方法では、文字列"dog,,cat"はトークンに分割されました。はStringTokenizer比較に含まれていますが、の目的の結果が返されないことに注意してください["dog", "", "cat]

トークン化は合計100万回繰り返され、方法の違いに気付くのに十分な時間がかかりました。

単純なベンチマークに使用されたコードは次のとおりです。

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

結果

テストはJavaSE6(ビルド1.6.0_12-b04)を使用して実行され、結果は次のとおりです。

                   実行1実行2実行3実行4​​実行5
                   ----- ----- ----- ----- -----
StringTokenizer 172188187172172
MyTokenizer 234 234 235 234 235
String.split 1172 1156 1171 1172 1156
Pattern.compile 906 891 891 907 906

したがって、限られたテストとわずか5回の実行からStringTokenizerわかるように、実際には最速でしたMyTokenizerが、2位になりました。次に、String.splitが最も遅く、プリコンパイルされた正規表現はsplitメソッドよりもわずかに高速でした。

他の小さなベンチマークと同様に、それはおそらく実際の状態をあまり表していないので、結果は塩の粒(またはマウンド)で取得する必要があります。

于 2009-06-12T14:13:15.003 に答える
4

注:いくつかの簡単なベンチマークを実行した後、ScannerはString.splitよりも約4倍遅いことがわかりました。したがって、スキャナーは使用しないでください。

(この場合、スキャナーが悪い考えであるという事実を記録するために投稿を残しておきます。(次のように読んでください:スキャナーを提案するために私に反対票を投じないでください...))

Java 1.5以降を使用していると仮定して、次のように実装するScannerIterator<String>を試してください。

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

与える:

dog

cat
于 2009-06-12T13:24:38.367 に答える
2

StringTokenizerの代わりに、ApacheCommonsLangのStrTokenizerクラスを試すことができます。

このクラスは、文字列を多くの小さな文字列に分割できます。StringTokenizerと同様の仕事をすることを目的としていますが、ListIteratorインターフェースの実装を含め、はるかに多くの制御と柔軟性を提供します。

空のトークンは削除されるか、nullとして返される場合があります。

これはあなたが必要としているもののように聞こえます、私は思いますか?

于 2009-06-12T13:24:38.210 に答える
2

トークン化する必要のある文字列の種類に応じて、たとえばString.indexOf()に基づいて独自のスプリッターを作成できます。文字列のトークン化は互いに独立しているため、パフォーマンスをさらに向上させるマルチコアソリューションを作成することもできます。コアごとに100文字列のバッチで作業します。String.split()またはその他の方法を実行します。

于 2009-06-12T13:18:59.130 に答える
1

あなたはそのようなことをすることができます。完璧ではありませんが、うまくいくかもしれません。

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

可能であれば、Listを省略して、部分文字列に対して直接何かを行うことができます。

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

私のシステムでは、最後のメソッドはStringTokenizerソリューションよりも高速ですが、それがどのように機能するかをテストすることをお勧めします。(もちろん、2番目のwhileの{}を省略して、このメソッドを少し短くすることもできます。もちろん、外側のwhileループの代わりにforループを使用して、最後のi ++を含めることもできますが、私はしませんでした。私はその悪いスタイルを考えるので、ここでそれをします。

于 2009-06-12T13:59:14.600 に答える
0

さて、あなたができる最速のことは、手動で文字列をトラバースすることです、例えば

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

この(非公式のテスト)は、分割の2倍の速さのように見えます。ただし、この方法で繰り返すのは少し危険です。たとえば、エスケープされたコンマで壊れてしまい、ある時点で(10億の文字列のリストに3つのエスケープされたコンマがあるため)それを処理する必要が生じた場合は、それを考慮に入れると、おそらく速度の利点の一部を失うことになります。

結局、それはおそらくわざわざする価値はありません。

于 2009-06-12T14:05:09.737 に答える
0

GoogleのGuavaをお勧めしますSplittercoobird
テストと 比較したところ、次の結果が得 られました。

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

于 2012-11-21T22:10:22.930 に答える
-1

入力が構造化されている場合は、JavaCCコンパイラを確認できます。入力を読み取るJavaクラスを生成します。次のようになります。

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

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


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

dog: <DOG>
   {
   animals.add(new Animal("Dog"));
   }
于 2009-06-12T13:21:35.753 に答える