40

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

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

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

或者

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

10 回答 10

63

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

于 2011-05-11T14:22:56.933 回答
14

在 Java 7 中拆分只是为此输入调用 indexOf,请参阅源代码。拆分应该非常快,接近 indexOf 的重复调用。

于 2012-06-21T22:33:19.177 回答
6

Java API 规范建议使用split. 请参阅 的文档StringTokenizer

于 2011-05-11T14:27:13.010 回答
5

另一件重要的事情,据我所知,没有记录,是要求 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%。

于 2013-08-29T01:38:02.453 回答
3

使用拆分。

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

于 2011-05-11T14:22:46.650 回答
2

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

于 2011-05-12T00:09:23.560 回答
2

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

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

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

于 2011-05-11T14:27:11.967 回答
2

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

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

于 2013-08-30T08:13:09.233 回答
1

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

http://www.javamex.com/tutorials/regular_expressions/splitting_tokenisation_performance.shtml#.V6-CZvnhCM8
于 2016-08-13T20:26:59.130 回答
-1

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

在此处输入图像描述

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

于 2017-07-07T12:41:21.667 回答