50

私のアプリケーションの現在のコードは次のとおりです。

String[] ids = str.split("/");

アプリケーションのプロファイリングでは、文字列の分割に無視できない時間が費やされます。また、splitメソッドは正規表現を取りますが、ここでは不要です。

文字列の分割を最適化するために、どのような代替手段を使用できますか? StringUtils.split速いですか?

(自分で試してテストしたはずですが、アプリケーションのプロファイリングには時間がかかります。)

4

10 に答える 10

56

String.split(String)パターンの長さが 1 文字のみの場合、正規表現は作成されません。1 文字で分割する場合は、非常に効率的な特殊なコードが使用されます。StringTokenizerこの特定のケースでは、それほど高速ではありません。

これは、OpenJDK7/OracleJDK7 で導入されました。ここにバグレポートコミットがあります。ここで簡単なベンチマークを作成しました。


$ java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

$ java Split
split_banthar: 1231
split_tskuzzy: 1464
split_tskuzzy2: 1742
string.split: 1291
StringTokenizer: 1517
于 2012-06-12T18:13:33.737 に答える
22

サードパーティのライブラリを使用できる場合、Guava Splitterは正規表現を要求しない場合のオーバーヘッドが発生せず、原則として非常に高速です。(開示:私はGuavaに貢献しています。)

Iterable<String> split = Splitter.on('/').split(string);

(また、Splitterは原則としてよりもはるかに予測可能String.splitです。)

于 2012-06-12T17:10:40.350 に答える
9

StringTokenizerこのような単純な解析でははるかに高速です(しばらく前にベンチマークを行ったところ、大幅な高速化が得られました)。

StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = new String[st.countTokens()];
arr[0] = st.nextToken();

もう少しパフォーマンスを上げたい場合は、手動でも行うことができます。

String s = "1/2/3"
char[] c = s.toCharArray();
LinkedList<String> ll = new LinkedList<String>();
int index = 0;

for(int i=0;i<c.length;i++) {
    if(c[i] == '/') {
        ll.add(s.substring(index,i));
        index = i+1;
    }
}

String[] arr = ll.size();
Iterator<String> iter = ll.iterator();
index = 0;

for(index = 0; iter.hasNext(); index++)
    arr[index++] = iter.next();
于 2012-06-12T17:04:48.947 に答える
6

私は大規模に作業しているので、いくつかの独自の実装を含め、さらにベンチマークを提供すると役立つと思いました (スペースで分割しましたが、これは一般的にかかる時間を示しているはずです)。

私は 2622761 行の 426 MB のファイルを扱っています。唯一の空白は、通常のスペース (" ") と行 ("\n") です。

まず、すべての行をスペースに置き換え、1 つの巨大な行を解析してベンチマークを行います。

.split(" ")
Cumulative time: 31.431366952 seconds

.split("\s")
Cumulative time: 52.948729489 seconds

splitStringChArray()
Cumulative time: 38.721338004 seconds

splitStringChList()
Cumulative time: 12.716065893 seconds

splitStringCodes()
Cumulative time: 1 minutes, 21.349029036000005 seconds

splitStringCharCodes()
Cumulative time: 23.459840685 seconds

StringTokenizer
Cumulative time: 1 minutes, 11.501686094999997 seconds

次に、行ごとに分割するベンチマークを実行します (つまり、関数とループが一度にすべてではなく、何度も実行されることを意味します)。

.split(" ")
Cumulative time: 3.809014174 seconds

.split("\s")
Cumulative time: 7.906730124 seconds

splitStringChArray()
Cumulative time: 4.06576739 seconds

splitStringChList()
Cumulative time: 2.857809996 seconds

Bonus: splitStringChList(), but creating a new StringBuilder every time (the average difference is actually more like .42 seconds):
Cumulative time: 3.82026621 seconds

splitStringCodes()
Cumulative time: 11.730249921 seconds

splitStringCharCodes()
Cumulative time: 6.995555826 seconds

StringTokenizer
Cumulative time: 4.500008172 seconds

コードは次のとおりです。

// Use a char array, and count the number of instances first.
public static String[] splitStringChArray(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    int count = 0;
    for (char c : strArray) {
        if (c == ' ') {
            count++;
        }
    }
    String[] splitArray = new String[count+1];
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            splitArray[i] = sb.toString();
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return splitArray;
}

// Use a char array but create an ArrayList, and don't count beforehand.
public static ArrayList<String> splitStringChList(String str, StringBuilder sb) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    char[] strArray = str.toCharArray();
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return words;
}

// Using an iterator through code points and returning an ArrayList.
public static ArrayList<String> splitStringCodes(String str) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    IntStream is = str.codePoints();
    OfInt it = is.iterator();
    int cp;
    StringBuilder sb = new StringBuilder();
    while (it.hasNext()) {
        cp = it.next();
        if (cp == 32) {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

// This one is for compatibility with supplementary or surrogate characters (by using Character.codePointAt())
public static ArrayList<String> splitStringCharCodes(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    int cp;
    int len = strArray.length;
    for (int i=0; i<len; i++) {
        cp = Character.codePointAt(strArray, i);
        if (cp == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

これは私が StringTokenizer を使用した方法です:

    StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
    words = new String[tokenizer.countTokens()];
    int i = 0;
    while (tokenizer.hasMoreTokens()) {
        words[i] = tokenizer.nextToken();
        i++;
    }
于 2017-03-10T21:46:39.757 に答える
3

java.util.StringTokenizer(String str, String delim)この投稿によると、約 2 倍高速です。

ただし、アプリケーションが巨大な規模でない限り、split問題はありません (同じ投稿を参照してください。数ミリ秒で数千の文字列が引用されています)。

于 2012-06-12T17:05:22.460 に答える
2

Guava には、メソッドよりも柔軟なスプリッターString.split()があり、(必然的に) 正規表現を使用しません。OTOHString.split()は Java 7 で最適化され、区切り文字が 1 文字の場合に正規表現機構を回避します。そのため、パフォーマンスは Java 7 でも似ているはずです。

于 2012-06-12T17:11:03.003 に答える
1

StringTokenizer は他のどの分割方法よりも高速ですが、トークナイザーがトークン化された文字列と共に区切り文字を返すようにすると、パフォーマンスが 50% ほど向上します。これは、コンストラクターを使用して実現されjava.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)ます。ここで、その問題に関するいくつかの他の洞察: Java での StringTokenizer クラスと分割メソッドのパフォーマンス

于 2013-08-29T01:50:17.947 に答える
0

Stringのsplitメソッドは、おそらくより安全な選択です。少なくともjava6の時点で(APIリファレンスは7用ですが)、基本的にStringTokenizerの使用は推奨されていないと言われています。彼らの言葉遣いは以下に引用されています。

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

于 2012-06-12T17:11:57.273 に答える
0

分割関数を自分で作成できます。これが最速になります。ここにそれを証明するリンクがあります、それは私にとってもうまくいき、私のコードを6X最適化しました

StringTokenizer - 整数で行を読み取る

Split: 366 ミリ秒 IndexOf: 50 ミリ秒 StringTokenizer: 89 ミリ秒 GuavaSplit: 109 ミリ秒 IndexOf2 (上記の質問で指定された非常に最適化されたソリューション): 14 ミリ秒

于 2016-11-04T00:56:49.920 に答える