私のアプリケーションの現在のコードは次のとおりです。
String[] ids = str.split("/");
アプリケーションのプロファイリングでは、文字列の分割に無視できない時間が費やされます。また、split
メソッドは正規表現を取りますが、ここでは不要です。
文字列の分割を最適化するために、どのような代替手段を使用できますか? StringUtils.split
速いですか?
(自分で試してテストしたはずですが、アプリケーションのプロファイリングには時間がかかります。)
私のアプリケーションの現在のコードは次のとおりです。
String[] ids = str.split("/");
アプリケーションのプロファイリングでは、文字列の分割に無視できない時間が費やされます。また、split
メソッドは正規表現を取りますが、ここでは不要です。
文字列の分割を最適化するために、どのような代替手段を使用できますか? StringUtils.split
速いですか?
(自分で試してテストしたはずですが、アプリケーションのプロファイリングには時間がかかります。)
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
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();
私は大規模に作業しているので、いくつかの独自の実装を含め、さらにベンチマークを提供すると役立つと思いました (スペースで分割しましたが、これは一般的にかかる時間を示しているはずです)。
私は 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++;
}
java.util.StringTokenizer(String str, String delim)
この投稿によると、約 2 倍高速です。
ただし、アプリケーションが巨大な規模でない限り、split
問題はありません (同じ投稿を参照してください。数ミリ秒で数千の文字列が引用されています)。
Guava には、メソッドよりも柔軟なスプリッターString.split()
があり、(必然的に) 正規表現を使用しません。OTOHString.split()
は Java 7 で最適化され、区切り文字が 1 文字の場合に正規表現機構を回避します。そのため、パフォーマンスは Java 7 でも似ているはずです。
StringTokenizer は他のどの分割方法よりも高速ですが、トークナイザーがトークン化された文字列と共に区切り文字を返すようにすると、パフォーマンスが 50% ほど向上します。これは、コンストラクターを使用して実現されjava.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)
ます。ここで、その問題に関するいくつかの他の洞察: Java での StringTokenizer クラスと分割メソッドのパフォーマンス
Stringのsplitメソッドは、おそらくより安全な選択です。少なくともjava6の時点で(APIリファレンスは7用ですが)、基本的にStringTokenizerの使用は推奨されていないと言われています。彼らの言葉遣いは以下に引用されています。
「StringTokenizerは互換性の理由で保持されているレガシークラスですが、新しいコードでは使用しないでください。この機能を求める人は、代わりにStringのsplitメソッドまたはjava.util.regexパッケージを使用することをお勧めします。」
分割関数を自分で作成できます。これが最速になります。ここにそれを証明するリンクがあります、それは私にとってもうまくいき、私のコードを6X最適化しました
Split: 366 ミリ秒 IndexOf: 50 ミリ秒 StringTokenizer: 89 ミリ秒 GuavaSplit: 109 ミリ秒 IndexOf2 (上記の質問で指定された非常に最適化されたソリューション): 14 ミリ秒