【问题标题】:Performance of StringTokenizer class vs. String.split method in JavaJava 中 StringTokenizer 类与 String.split 方法的性能对比
【发布时间】:2011-08-23 08:57:00
【问题描述】:

在我的软件中,我需要将字符串拆分为单词。我目前有超过 19,000,000 个文档,每个文档超过 30 个单词。

以下两种方法中哪一种是最好的方法(就性能而言)?

StringTokenizer sTokenize = new StringTokenizer(s," ");
while (sTokenize.hasMoreTokens()) {

String[] splitS = s.split(" ");
for(int i =0; i < splitS.length; i++)

【问题讨论】:

  • 我估计是第一个,但你为什么不直接测量呢?
  • 我可以,但我也对解释感兴趣......
  • 如果有人说选项 X 最快怎么办?你会选择那个选项,或者只是为了确定,你会测试两者吗?如果是后者,为什么不立即这样做呢? :)
  • @John :请明确您的问题,您是否需要在 Tokenize 与 split 之间做得更好,或者您正在寻找最好的方法,而不是 Tokenize 与 split
  • @Damodar“在性能方面做到这一点的最佳方式”

标签: java performance stringtokenizer


【解决方案1】:

据我所知,另一件重要的事情没有记录,那就是要求 StringTokenizer 返回分隔符以及标记化的字符串(通过使用构造函数 StringTokenizer(String str, String delim, boolean returnDelims))也减少了处理时间。因此,如果您正在寻找性能,我建议您使用类似的东西:

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%。

【讨论】:

    【解决方案2】:

    在性能方面 StringTokeniser 比拆分要好得多。检查下面的代码,

    但根据 Java 文档,不鼓励使用它。检查Here

    【讨论】:

    • 你只是实例化它,但之后你必须得到所有需要时间的令牌(当然它会比拆分的少)
    【解决方案3】:

    这可能是使用 1.6.0 的合理基准测试

    http://www.javamex.com/tutorials/regular_expressions/splitting_tokenisation_performance.shtml#.V6-CZvnhCM8
    

    【讨论】:

      【解决方案4】:

      不管它的旧状态如何,我希望 StringTokenizer 在这个任务中比 String.split() 快得多,因为它不使用正则表达式:它只是直接扫描输入,就像你自己通过 @ 987654323@。事实上,String.split() 每次调用它时都必须编译正则表达式,因此它甚至不如自己直接使用正则表达式高效。

      【讨论】:

      • 从其他地方读到,String.split 不会每次都重新编译,它可能会重新编译,但并非适用于所有情况。
      【解决方案5】:

      在运行微型(在这种情况下,甚至是纳米)基准测试时,有很多因素会影响您的结果。 JIT 优化和垃圾收集仅举几例。

      为了从微基准测试中获得有意义的结果,请查看jmh 库。它包含有关如何运行良好基准的优秀示例。

      【讨论】:

        【解决方案6】:

        Java 7 中的Split 只为此输入调用 indexOf,see the source。拆分应该很快,接近 indexOf 的重复调用。

        【讨论】:

        • 你确定吗?我可以在您提供的链接下的第 2361 行看到:return Pattern.compile(regex).split(this, limit);
        • 实施于 1770 年。
        • 如果正则表达式满足某些条件,实现将使用(即indexOf),否则将使用Pattern.compile(regex).split(this, limit);。来源:fastpath if the regex is a (1)one-char String and this character is not one of the RegEx's meta characters ".$|()[{^?*+\\", or (2)two-char String and the first char is the backslash and the second is not the ascii digit or ascii letter. 但正如其他地方所指出的,这是一个实现细节,因此不应依赖。
        【解决方案7】:

        Java API 规范建议使用split。请参阅documentation of StringTokenizer

        【讨论】:

        • @downvoters :请明确上述问题,您是否需要更好的 Tokenize 与 split 或者您正在寻找最好的方法,而不是 Tokenize 与 split
        • 问题很清楚,他正在寻找性能方面的最佳方法。 API 建议拆分,但没有提到(根据我通过 Google 找到的所有其他内容)Tokenize 表现更好。
        • @Bill,对不起,我的错误。那么他们可能会改变问题的标题
        【解决方案8】:

        如果您的数据已经在数据库中,您需要解析字符串,我建议重复使用 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)

        如果你想提高性能,我建议你的文件要少得多。例如使用数据库。如果您不想使用 SQL 数据库,我建议使用其中一种 http://nosql-database.org/

        【讨论】:

        • 有趣的是,我运行了你的代码,split 在我的机器上花费的时间始终是StringTokenizer 的两倍。 indexof 需要一半的时间。
        • 扫描仪和字符串分词器使用更灵活的模式/正则表达式,但不如仅查找特定字符高效。
        • @Peter Lawrey:StringTokenizer 不使用正则表达式。
        • @tjjjohnson Java 7 split 的操作类似于 indexOf 上的一系列操作,但仅限于有限但非常常见的操作。
        • 仅作记录,您对 indexOf 循环的实现不正确,您缺少最后一个分隔符之后的部分。不确定这对性能影响很大,但无论如何
        【解决方案9】:

        19,000,000 份文件在那里有什么用?您是否必须定期拆分所有文档中的单词?还是单发问题?

        如果您一次显示/请求一个文档,只有 30 个单词,这是一个非常小的问题,任何方法都可以工作。

        如果您必须一次处理所有文档,只有 30 个单词,这是一个非常小的问题,无论如何您更有可能受到 IO 限制。

        【讨论】:

          【解决方案10】:

          使用拆分。

          StringTokenizer 是一个遗留类,出于兼容性原因保留,但不鼓励在新代码中使用它。建议任何寻求此功能的人改用 split 方法。

          【讨论】:

          • 为什么是 -1 ?这确实正确回答了是否使用 split 或 StringTokenizer 的问题。规范确实提到,建议使用 Split 而不是 StringTokenizer
          • 在 Damodar 的回答中查看我的 cmets。该规范没有说明性能,这就是这个问题所要问的。
          • 谢谢比尔。 - 理性之春
          • 我投了反对票,因为它不询问是否使用其中一个,而是询问哪个更快。
          猜你喜欢
          • 1970-01-01
          • 2010-10-16
          • 2013-04-23
          • 2010-11-02
          • 1970-01-01
          • 1970-01-01
          • 2021-09-03
          • 2017-10-12
          • 2010-10-18
          相关资源
          最近更新 更多