在修补StringTokenizer
类之后,我找不到满足返回要求的方法["dog", "", "cat"]
。
此外,StringTokenizer
仅出于兼容性原因才保留该类,并String.split
鼓励使用 。来自 API 规范StringTokenizer
:
StringTokenizer
是一个遗留类,出于兼容性原因保留,但不鼓励在新代码中使用它。建议任何寻求此功能的人使用split
方法String
或java.util.regex
包来代替。
由于问题是该String.split
方法据称性能不佳,因此我们需要找到替代方法。
注意:我说的是“据说性能很差”,因为很难确定每个用例都会导致StringTokenizer
优于该String.split
方法。此外,在许多情况下,除非字符串的标记化确实是通过适当的分析确定的应用程序的瓶颈,否则我觉得它最终会成为过早的优化,如果有的话。在进行优化之前,我倾向于说编写有意义且易于理解的代码。
现在,从目前的需求来看,滚动我们自己的标记器可能不会太难。
推出我们自己的 tokenzier!
以下是我写的一个简单的分词器。我应该注意到没有速度优化,也没有错误检查来防止超过字符串的末尾——这是一个快速而肮脏的实现:
class MyTokenizer implements Iterable<String>, Iterator<String> {
String delim = ",";
String s;
int curIndex = 0;
int nextIndex = 0;
boolean nextIsLastToken = false;
public MyTokenizer(String s, String delim) {
this.s = s;
this.delim = delim;
}
public Iterator<String> iterator() {
return this;
}
public boolean hasNext() {
nextIndex = s.indexOf(delim, curIndex);
if (nextIsLastToken)
return false;
if (nextIndex == -1)
nextIsLastToken = true;
return true;
}
public String next() {
if (nextIndex == -1)
nextIndex = s.length();
String token = s.substring(curIndex, nextIndex);
curIndex = nextIndex + 1;
return token;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
将MyTokenizer
采用 aString
来标记化和 aString
作为分隔符,并使用该String.indexOf
方法执行对分隔符的搜索。令牌由该String.substring
方法生成。
我怀疑通过在char[]
级别而不是级别上处理字符串可能会有一些性能改进String
。但我会把它作为练习留给读者。
该类还实现了Iterable
andIterator
为了利用for-each
Java 5 中引入的循环构造。StringTokenizer
is an Enumerator
, 并且不支持该for-each
构造。
是不是更快了?
为了确定这是否更快,我编写了一个程序来比较以下四种方法的速度:
- 的使用
StringTokenizer
。
- 使用新的
MyTokenizer
.
- 的使用
String.split
。
- 使用预编译的正则表达式
Pattern.compile
。
在这四种方法中,字符串"dog,,cat"
被分成了标记。尽管StringTokenizer
比较中包含 ,但应该注意的是,它不会返回 的所需结果["dog", "", "cat]
。
标记化总共重复了 100 万次,以便有足够的时间来注意到方法的差异。
用于简单基准测试的代码如下:
long st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
StringTokenizer t = new StringTokenizer("dog,,cat", ",");
while (t.hasMoreTokens()) {
t.nextToken();
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
MyTokenizer mt = new MyTokenizer("dog,,cat", ",");
for (String t : mt) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
String[] tokens = "dog,,cat".split(",");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
Pattern p = Pattern.compile(",");
for (int i = 0; i < 1e6; i++) {
String[] tokens = p.split("dog,,cat");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
结果
测试使用 Java SE 6(内部版本 1.6.0_12-b04)运行,结果如下:
运行 1 运行 2 运行 3 运行 4 运行 5
----- ----- ----- ----- -----
字符串标记器 172 188 187 172 172
MyTokenizer 234 234 235 234 235
字符串拆分 1172 1156 1171 1172 1156
Pattern.compile 906 891 891 907 906
因此,从有限的测试和仅五次运行中可以看出,StringTokenizer
实际上确实是最快的,但MyTokenizer
排在第二位。然后,String.split
是最慢的,预编译的正则表达式比split
方法略快。
与任何小基准一样,它可能不是很能代表现实生活中的条件,因此应该使用一粒(或一堆)盐来获取结果。