在尝试使用StringTokenizer
类后,我无法找到满足返回["dog", "", "cat"]
要求的方法。此外,StringTokenizer
类仅用于兼容性原因,鼓励使用String.split
。从StringTokenizer
的API规范中可以看出:
StringTokenizer
是一个遗留类,为了兼容性而保留,虽然它的使用在新代码中被不推荐。建议任何寻求此功能的人使用String
的split
方法或java.util.regex
包。
由于问题在于String.split
方法的表现较差,我们需要找到替代方案。
注意:我说"表现较差"是因为很难确定每种情况下StringTokenizer
都优于String.split
方法。此外,在许多情况下,除非字符串的分词确实是由适当的分析确定的应用程序的瓶颈,否则我认为这最终将成为过早的优化,如果有的话,我倾向于编写有意义且易于理解的代码,而不是进行优化。
现在,根据当前的需求,可能自己编写分词器并不太困难。
自己编写分词器!
下面是我编写的一个简单的分词器。需要注意的是,没有速度优化,也没有错误检查来防止超出字符串的末尾 - 这只是一个快速而简单的实现:
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
将使用一个String
作为分词器,另一个String
作为分隔符,并使用String.indexOf
方法来查找分隔符。通过String.substring
方法产生令牌。
我认为通过在char[]
级别而不是String
级别上处理字符串可能会有一些性能改进。但我将把这个任务留给读者自行完成。
该类还实现了Iterable
和Iterator
以利用Java 5中引入的for-each
循环结构。 StringTokenizer
是一个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 (build 1.6.0_12-b04)运行,结果如下:
运行1 运行2 运行3 运行4 运行5
----- ----- ----- ----- -----
StringTokenizer 172 188 187 172 172
MyTokenizer 234 234 235 234 235
String.split 1172 1156 1171 1172 1156
Pattern.compile 906 891 891 907 906
因此,从有限的测试和仅五次运行可以看出,StringTokenizer
确实是最快的,但MyTokenizer
排名第二。然后,String.split
是最慢的,而预编译的正则表达式比split
方法稍快。
与任何小基准测试一样,它可能不太代表实际情况,因此应该对结果持怀疑态度。