使用StringTokenizer复制String.split

Dan*_*ani 5 java string performance split stringtokenizer

通过鼓励这个,事实上我有十亿串的解析,我想修改我的代码接受的StringTokenizer代替的String []

我和你之间唯一能够获得美味的x2性能提升的事实就是你正在做的事情

"dog,,cat".split(",")
//output: ["dog","","cat"]

StringTokenizer("dog,,cat")
// nextToken() = "dog"
// nextToken() = "cat"
Run Code Online (Sandbox Code Playgroud)

如何使用StringTokenizer获得类似的结果?有没有更快的方法来做到这一点?

Jon*_*eet 12

你只是用逗号标记吗?如果是这样,我会编写自己的标记化器 - 它可能最终比更通用的StringTokenizer更有效,它可以查找多个标记,并且您可以使它按照您的喜好行事.对于这样一个简单的用例,它可以是一个简单的实现.

如果它有用,你甚至可以Iterable<String>通过强类型而不是提供的Enumeration支持来实现并获得增强的for-loop支持StringTokenizer.如果你想要任何帮助编码这样的野兽,请告诉我 - 这真的不应该太难.

此外,我会尝试在实际数据上运行性能测试,然后再跳过现有解决方案.您是否知道实际花费了多少执行时间String.split?我知道你有很多字符串要解析,但是如果你之后做了一些重要的事情,那么我希望它比分裂更重要.


coo*_*ird 10

在修补StringTokenizer课程后,我找不到满足要求返回的方法["dog", "", "cat"].

此外,StringTokenizer仅出于兼容性原因而保留该类,并且String.split鼓励使用该类.来自API规范StringTokenizer:

StringTokenizer是一个遗留类,出于兼容性原因而保留,尽管在新代码中不鼓励使用它.建议任何寻求此功能的人都使用split方法Stringjava.util.regex 包.

由于问题是该String.split方法的表现不佳,我们需要找到一个替代方案.

注意:我说的是"据说表现不佳",因为很难确定每个用例都会导致StringTokenizer优于该String.split方法.此外,在许多情况下,除非字符串的标记化确实是通过适当的分析确定的应用程序的瓶颈,否则我觉得它最终会成为过早的优化,如果有的话.在冒险进行优化之前,我倾向于说编写有意义且易于理解的代码.

现在,根据当前的要求,可能滚动我们自己的标记器并不会太困难.

滚动我们自己的tokenzier!

以下是我写的一个简单的tokenizer.我应该注意,没有速度优化,也没有错误检查以防止超过字符串的结尾 - 这是一个快速而肮脏的实现:

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

MyTokenizer将采取String来标记和String作为分隔符,并使用该String.indexOf方法来执行分隔符搜索.令牌由该String.substring方法产生.

我怀疑通过在char[]级别而不是在String级别上处理字符串可能会有一些性能改进.但我会把它作为练习留给读者.

该类还实现Iterable并且Iterator为了利用for-eachJava 5中引入的循环结构StringTokenizer是一个Enumerator,并且不支持该for-each构造.

它更快吗?

为了找出这是否更快,我写了一个程序来比较以下四种方法的速度:

  1. 使用StringTokenizer.
  2. 使用新的MyTokenizer.
  3. 使用String.split.
  4. 使用预编译的正则表达式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);
Run Code Online (Sandbox Code Playgroud)

结果

测试使用Java SE 6(build 1.6.0_12-b04)运行,结果如下:

                   Run 1    Run 2    Run 3    Run 4    Run 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方法稍快.

与任何小基准一样,它可能不具备现实生活条件的代表性,因此结果应采用谷物(或土墩)盐.