在我的软件中,我需要将字符串拆分为单词。目前我有超过19,000,000个文档,每个文档都有30个以上的单词。
以下两种方式哪种是最好的方式(从性能方面考虑)?
StringTokenizer sTokenize = new StringTokenizer(s," ");
while (sTokenize.hasMoreTokens()) {
或者
String[] splitS = s.split(" ");
for(int i =0; i < splitS.length; i++)
在我的软件中,我需要将字符串拆分为单词。目前我有超过19,000,000个文档,每个文档都有30个以上的单词。
以下两种方式哪种是最好的方式(从性能方面考虑)?
StringTokenizer sTokenize = new StringTokenizer(s," ");
while (sTokenize.hasMoreTokens()) {
或者
String[] splitS = s.split(" ");
for(int i =0; i < splitS.length; i++)
如果您的数据已经在数据库中,您需要解析单词字符串,我建议反复使用indexOf函数。它比另外两种解决方案快得多。
然而,从数据库获取数据仍然很可能更加昂贵。
StringBuilder sb = new StringBuilder();
for (int i = 100000; i < 100000 + 60; i++)
sb.append(i).append(' ');
String sample = sb.toString();
int runs = 100000;
for (int i = 0; i < 5; i++) {
{
long start = System.nanoTime();
for (int r = 0; r < runs; r++) {
StringTokenizer st = new StringTokenizer(sample);
List<String> list = new ArrayList<String>();
while (st.hasMoreTokens())
list.add(st.nextToken());
}
long time = System.nanoTime() - start;
System.out.printf("StringTokenizer took an average of %.1f us%n", time / runs / 1000.0);
}
{
long start = System.nanoTime();
Pattern spacePattern = Pattern.compile(" ");
for (int r = 0; r < runs; r++) {
List<String> list = Arrays.asList(spacePattern.split(sample, 0));
}
long time = System.nanoTime() - start;
System.out.printf("Pattern.split took an average of %.1f us%n", time / runs / 1000.0);
}
{
long start = System.nanoTime();
for (int r = 0; r < runs; r++) {
List<String> list = new ArrayList<String>();
int pos = 0, end;
while ((end = sample.indexOf(' ', pos)) >= 0) {
list.add(sample.substring(pos, end));
pos = end + 1;
}
}
long time = System.nanoTime() - start;
System.out.printf("indexOf loop took an average of %.1f us%n", time / runs / 1000.0);
}
}
打印
StringTokenizer took an average of 5.8 us
Pattern.split took an average of 4.8 us
indexOf loop took an average of 1.8 us
StringTokenizer took an average of 4.9 us
Pattern.split took an average of 3.7 us
indexOf loop took an average of 1.7 us
StringTokenizer took an average of 5.2 us
Pattern.split took an average of 3.9 us
indexOf loop took an average of 1.8 us
StringTokenizer took an average of 5.1 us
Pattern.split took an average of 4.1 us
indexOf loop took an average of 1.6 us
StringTokenizer took an average of 5.0 us
Pattern.split took an average of 3.8 us
indexOf loop took an average of 1.6 us
打开一个文件的成本约为8毫秒。由于文件非常小,缓存可以将性能提高2-5倍。即使如此,它仍将花费大约10小时打开文件。相比之下,使用split与StringTokenizer的成本要低得多,每个的成本不到0.01毫秒。解析1900万x 30个单词*每个单词8个字母应该需要大约10秒钟(每2秒约1 GB)。split
的执行时间大约是StringTokenizer
的两倍。而indexOf
的执行时间则只有其一半。 - Bill the Lizard在Java 7中,Split仅对此输入调用indexOf函数。查看源代码。 Split应该非常快,接近于重复调用indexOf。
return Pattern.compile(regex).split(this, limit);
- Krzysztof WolnyindexOf
),如果正则表达式满足某些条件,则将使用Pattern.compile(regex).split(this, limit);
。从源代码中可以看出:如果正则表达式是(1)一个字符的字符串,并且该字符不是RegEx的元字符“.$|()[{^?*+\”之一,或者(2)两个字符的字符串,第一个字符是反斜杠,第二个字符不是ASCII数字或ASCII字母,则采用快速路径。但是正如其他地方指出的那样,这是一个实现细节,因此不应依赖它。 - hendalstsplit
函数。请参考StringTokenizer
的文档。另一个重要的事情,据我注意到并未记录在案的是,通过使用构造函数 StringTokenizer(String str, String delim, boolean returnDelims)
要求StringTokenizer返回分隔符和已分词字符串也可以减少处理时间。因此,如果您正在寻求性能,我建议使用以下代码:
private static final String DELIM = "#";
public void splitIt(String input) {
StringTokenizer st = new StringTokenizer(input, DELIM, true);
while (st.hasMoreTokens()) {
String next = getNext(st);
System.out.println(next);
}
}
private String getNext(StringTokenizer st){
String value = st.nextToken();
if (DELIM.equals(value))
value = null;
else if (st.hasMoreTokens())
st.nextToken();
return value;
}
尽管getNext()方法会引入一些开销,但根据我的基准测试结果,它仍然比原来快50%。
使用split方法。
StringTokenizer是一个保留下来的遗留类,为了兼容性而存在,虽然在新代码中不建议使用它。建议任何寻求此功能的人使用split方法代替。
这 1900 万份文件有什么作用?您是否需要定期拆分所有文档中的单词?还是这只是一个一次性的问题?
如果您一次只显示/请求一个文档,每个文档只有 30 个单词,那么这是一个非常微小的问题,任何方法都可以解决。
如果您需要一次处理所有文档,每个文档只有 30 个单词,那么这是一个非常微小的问题,您更有可能受到 IO 限制。
尽管它是传统的,但我希望StringTokenizer
在这个任务中比String.split()
快得多,因为它不使用正则表达式:它直接扫描输入,就像你自己通过indexOf()
一样。事实上,每次调用String.split()
时都必须编译正则表达式,所以它甚至不如直接使用正则表达式本身高效。
在运行微基准测试(包括纳米级别的测试)时,有很多因素会影响您的结果。JIT 优化和垃圾回收只是其中之一。
为了从微基准测试中获得有意义的结果,请查看 jmh 库。它附带了如何运行好基准测试的示例。
http://www.javamex.com/tutorials/regular_expressions/splitting_tokenisation_performance.shtml#.V6-CZvnhCM8