这是我应用程序中的当前代码:
String[] ids = str.split("/");
在对应用进行性能分析时,有相当一部分时间会花费在字符串拆分上。此外,split
方法接受一个正则表达式,在这里是多余的。
我可以使用什么替代方法来优化字符串拆分?StringUtils.split
更快吗?
(我本可以自己尝试并测试,但分析我的应用程序需要很长时间。)
这是我应用程序中的当前代码:
String[] ids = str.split("/");
在对应用进行性能分析时,有相当一部分时间会花费在字符串拆分上。此外,split
方法接受一个正则表达式,在这里是多余的。
我可以使用什么替代方法来优化字符串拆分?StringUtils.split
更快吗?
(我本可以自己尝试并测试,但分析我的应用程序需要很长时间。)
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 = 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();
String
类的split
方法和java.util.regex
包使用正则表达式会产生相当大的开销。而StringTokenizer
则不会。 - Louis Wasserman考虑到我正在处理大规模的数据,我认为提供更多基准测试会有所帮助,包括一些我自己的实现(我是按空格分割的,但这应该说明了通常需要多长时间):
我正在处理一个大小为426 MB,共2622761行的文件。唯一的空白字符是普通的空格(" ")和换行符("\n")。
首先,我将所有换行符替换为空格,并对解析一个巨大的行进行基准测试:
.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 tokenizer = new StringTokenizer(file.getCurrentString());
words = new String[tokenizer.countTokens()];
int i = 0;
while (tokenizer.hasMoreTokens()) {
words[i] = tokenizer.nextToken();
i++;
}
if (sb.length() > 0) words.add(sb.toString());
另外:java.util.StringTokenizer(String str, String delim)
据这篇帖子称,速度大约快了一倍。
然而,除非您的应用程序规模巨大,否则使用split
是可以的(参见同一帖子,引用数千个字符串只需几毫秒)。
Guava提供了一个Splitter,比String.split()
方法更灵活,并且不一定使用正则表达式。然而,在Java 7中,String.split()
已经被优化,如果分隔符是单个字符,则避免使用正则表达式引擎。因此,在Java 7中性能应该相似。
StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
StringTokenizer比任何其他分割方法都要快,但是让分词器返回分隔符和标记化字符串可以提高大约50%的性能。这可以通过使用构造函数java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims)
来实现。以下是有关此问题的其他见解:Java中StringTokenizer类与split方法的性能比较
你可以自己编写split函数,这将是最快的解决方案。下面的链接证明了它的效果,我也亲测有效,可以优化我的代码6倍。
StringTokenizer - reading lines with integers
Split: 366毫秒 IndexOf: 50毫秒 StringTokenizer: 89毫秒 GuavaSplit: 109毫秒 IndexOf2(上述问题中提供的某些超级优化解决方案):14毫秒 CsvMapperSplit(逐行映射):326毫秒 CsvMapperSplit_DOC(构建一个文档并一次性映射所有行):177毫秒
split
方法内创建正则表达式,仅使用一个字符长度的模式是不够的。该字符还不能是正则表达式元字符 ".$|()[{^?*+\"之一,例如split(".")
将创建/编译正则表达式模式。(在至少jdk8上进行了验证) - andrii