これが私のアプリケーションの現在のコードです。
String[] ids = str.split("/");
アプリケーションのプロファイリング時に、文字列の分割に無視できない時間が費やされていることに気付きました。
また、split
は実際には正規表現を使用することを学びましたが、これはここでは役に立ちません。
だから私の質問は、文字列の分割を最適化するためにどのような代替手段を使用できますか?私はStringUtils.split
しかし、それは速いですか?
(私は自分で試してみましたが、アプリケーションのプロファイリングには多くの時間がかかります。だから誰かがすでに時間の節約になる答えを知っているなら)
パターンの長さが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
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();
Java.util.StringTokenizer(String str, String delim)
は this post に従って約2倍の速度です。
ただし、アプリケーションが巨大な規模でない限り、split
で十分です(同じ投稿、数ミリ秒で数千の文字列を引用します)。
私が大規模に作業しているのを見て、自分の実装のいくつかを含むいくつかのベンチマークを提供するのに役立つと思いました(スペースで分割しますが、これは一般にどのくらい時間がかかるかを説明する必要があります):
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++;
}
Guavaには Splitter があり、これはString.split()
メソッドよりも柔軟性があり、(必ずしも)正規表現を使用しません。 OTOH、String.split()
は、Java 7で最適化され、セパレータが単一の文字である場合に正規表現機構を回避します。したがって、パフォーマンスはJava 7。
StringTokenizerは他のどの分割方法よりも高速ですが、トークナイザーにトークン化された文字列とともに区切り文字を返すようにすると、パフォーマンスが50%程度向上します。これは、コンストラクターJava.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)
を使用して実現されます。ここで、その点に関する他の洞察をいくつか示します。 JavaのStringTokenizerクラスとsplitメソッドのパフォーマンス
分割関数は自分で記述できますが、これが最速になります。これがそれを証明するリンクです
Split:366ms IndexOf:50ms StringTokenizer:89ms GuavaSplit:109ms IndexOf2(上記の質問で与えられたいくつかの超最適化ソリューション):14ms CsvMapperSplit(行ごとにマッピング):326ms CsvMapperSplit_DOC(1つのドキュメントを作成し、すべての行を一度にマッピング):177ms
Stringのsplitメソッドはおそらくより安全な選択です。 少なくともJava 6 (APIリファレンスは7ですが)として、彼らは基本的にStringTokenizerの使用は推奨されていないと言います。それらの文言は以下に引用されています。
「StringTokenizerは、互換性の理由で保持されるレガシークラスです。新しいコードでは使用を推奨していません。この機能を探している人は、StringまたはJava.utilのsplitメソッドを使用することをお勧めします。代わりにregexパッケージ。 "