Java 正在使用StringTokenizer复制String.split

Java 正在使用StringTokenizer复制String.split,java,performance,string,split,stringtokenizer,Java,Performance,String,Split,Stringtokenizer,受此鼓舞,并且我有数十亿个字符串需要解析,我尝试修改代码以接受StringTokenizer而不是string[] 在我和得到美味的x2性能提升之间剩下的唯一一件事是,当你 "dog,,cat".split(",") //output: ["dog","","cat"] StringTokenizer("dog,,cat") // nextToken() = "dog" // nextToken() = "cat" 如何使用StringTokenizer获得类似的结果?有没有更快的方法可以做

受此鼓舞,并且我有数十亿个字符串需要解析,我尝试修改代码以接受StringTokenizer而不是string[]

在我和得到美味的x2性能提升之间剩下的唯一一件事是,当你

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

StringTokenizer("dog,,cat")
// nextToken() = "dog"
// nextToken() = "cat"

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

您是否只在逗号上标记?如果是这样的话,我会编写自己的标记器-它可能会比更通用的StringTokenizer更高效,后者可以查找多个标记,并且您可以让它按照自己的意愿运行。对于这样一个简单的用例,它可以是一个简单的实现

如果有用的话,您甚至可以实现
Iterable
,并通过强类型而不是
StringTokenizer
提供的
Enumeration
支持来增强for循环支持。让我知道如果你想要任何帮助编码这样一个野兽-它真的不应该太难


此外,在脱离现有解决方案太远之前,我会尝试对实际数据运行性能测试。您知道有多少执行时间实际上花在了
String.split
?我知道你有很多字符串要解析,但是如果你以后对它们做了任何有意义的事情,我希望这比拆分更重要。

根据需要标记的字符串类型,你可以基于String.indexOf()编写自己的拆分器。您还可以创建一个多核解决方案来进一步提高性能,因为字符串的标记化是相互独立的。工作批量-让我们说-100字符串每个核心。执行String.split()或其他操作。

如果您的输入是结构化的,您可以查看JavaCC编译器。它生成一个java类来读取您的输入。它看起来是这样的:

TOKEN { <CAT: "cat"> , <DOG:"gog"> }

input: (cat() | dog())*


cat: <CAT>
   {
   animals.add(new Animal("Cat"));
   }

dog: <DOG>
   {
   animals.add(new Animal("Dog"));
   }
TOKEN{,}
输入:(cat()| dog())*
类别:
{
动物。添加(新动物(“猫”);
}
狗:
{
动物。添加(新动物(“狗”);
}

您可以尝试Apache Commons Lang中的StrTokenizer类,而不是StringTokenizer,我引用它:

此类可以将字符串拆分为许多较小的字符串。它旨在完成与StringTokenizer类似的工作,但是它提供了更多的控制和灵活性,包括实现ListIterator接口

空令牌可以被删除或作为null返回


这听起来像是您需要的,我想?

注意:在做了一些快速基准测试之后,Scanner的速度比String.split慢四倍左右。因此,不要使用扫描仪。

(我留下这篇帖子是为了记录这样一个事实:在这种情况下,扫描仪是个坏主意。)

假设您使用的是Java 1.5或更高版本,请尝试实现
迭代器
,具体如下:

Scanner sc = new Scanner("dog,,cat");
sc.useDelimiter(",");
while (sc.hasNext()) {
    System.out.println(sc.next());
}
给出:

dog

cat

你可以这样做。这并不完美,但可能对你有用

public static List<String> find(String test, char c) {
    List<String> list = new Vector<String>();
    start;
    int i=0;
    while (i<=test.length()) {
        int start = i;
        while (i<test.length() && test.charAt(i)!=c) {
            i++;
        }
        list.add(test.substring(start, i));
        i++;
    }
    return list;
}
公共静态列表查找(字符串测试,字符c){
列表=新向量();
开始
int i=0;

而(i嗯,你能做的最快的事情就是手动遍历字符串,例如

List<String> split(String s) {
        List<String> out= new ArrayList<String>();
           int idx = 0;
           int next = 0;
        while ( (next = s.indexOf( ',', idx )) > -1 ) {
            out.add( s.substring( idx, next ) );
            idx = next + 1;
        }
        if ( idx < s.length() ) {
            out.add( s.substring( idx ) );
        }
               return out;
    }
列表拆分(字符串s){
List out=new ArrayList();
int-idx=0;
int next=0;
而((next=s.indexOf(',',idx))>-1){
添加(s.substring(idx,next));
idx=下一个+1;
}
如果(idx
这个(非正式测试)看起来是split的两倍。但是,以这种方式进行迭代有点危险,例如,它会在转义逗号上中断,如果您最终需要在某个点上处理它(因为您的10亿字符串列表有3个转义逗号)当你考虑到这一点时,你可能最终会失去一些速度优势

最终,它可能不值得这么麻烦。

在修改了这个类之后,我找不到一种方法来满足返回
[“dog”,“cat”]
的要求

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

StringTokenizer
是一个遗留类 这是为了兼容性而保留的 原因虽然它的使用是有限的 在新代码中不鼓励。它是 建议任何寻求此服务的人 功能使用
split
方法
String
java.util.regex
改为打包

由于问题在于该方法的性能较差,因此我们需要找到替代方法

注意:我说的是“假定性能差”因为很难确定每个用例都会导致
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();
  }
}
结果

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

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 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 运行1运行2运行3 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