web-dev-qa-db-ja.com

Java String string performances

これが私のアプリケーションの現在のコードです。

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

アプリケーションのプロファイリング時に、文字列の分割に無視できない時間が費やされていることに気付きました。

また、splitは実際には正規表現を使用することを学びましたが、これはここでは役に立ちません。

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

(私は自分で試してみましたが、アプリケーションのプロファイリングには多くの時間がかかります。だから誰かがすでに時間の節約になる答えを知っているなら)

40
Matthieu Napoli

パターンの長さが1文字しかない場合、String.split(String)は正規表現を作成しません。単一の文字で分割する場合、非常に効率的な特別なコードを使用します。この特定の場合、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
40
Piotr Praszmo

サードパーティのライブラリを使用できる場合、 Guava'sSplitter は、要求しない場合に正規表現のオーバーヘッドを引き起こさず、原則として非常に高速です。 (開示:グアバに貢献します。)

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

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

18
Louis Wasserman

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

StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = 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();
8
tskuzzy

Java.util.StringTokenizer(String str, String delim)this post に従って約2倍の速度です。

ただし、アプリケーションが巨大な規模でない限り、splitで十分です(同じ投稿、数ミリ秒で数千の文字列を引用します)。

3
Michael

私が大規模に作業しているのを見て、自分の実装のいくつかを含むいくつかのベンチマークを提供するのに役立つと思いました(スペースで分割しますが、これは一般にどのくらい時間がかかるかを説明する必要があります):

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++;
    }
3
Joshua Mathias

Guavaには Splitter があり、これはString.split()メソッドよりも柔軟性があり、(必ずしも)正規表現を使用しません。 OTOH、String.split()は、Java 7で最適化され、セパレータが単一の文字である場合に正規表現機構を回避します。したがって、パフォーマンスはJava 7。

2
JB Nizet

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

2
cristianoms

分割関数は自分で記述できますが、これが最速になります。これがそれを証明するリンクです

StringTokenizer-整数で行を読み取る

Split:366ms IndexOf:50ms StringTokenizer:89ms GuavaSplit:109ms IndexOf2(上記の質問で与えられたいくつかの超最適化ソリューション):14ms CsvMapperSplit(行ごとにマッピング):326ms CsvMapperSplit_DOC(1つのドキュメントを作成し、すべての行を一度にマッピング):177ms

0
sanketshah

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

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

0
John Kane