【问题标题】:StringTokenizer - reading lines with integersStringTokenizer - 用整数读取行
【发布时间】:2013-10-21 18:28:35
【问题描述】:

我有一个关于优化我的代码的问题(它有效但太慢了......)。我正在阅读表单中的输入

X1 Y1
X2 Y2
etc

其中 Xi, Yi 是整数。我使用bufferedReader 读取行,然后使用StringTokenizer 处理这些数字,如下所示:

StringTokenizer st = new StringTokenizer(line, " ");

int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());

问题在于,这种方法在处理大型数据集时似乎效率低下。您能否建议我进行一些简单的改进(我听说可以使用一些整数解析 int 或正则表达式)以提高性能?感谢您的任何提示

编辑:也许我误判了自己,必须在代码的其他地方进行一些改进......

【问题讨论】:

  • 为什么感觉时间效率低?您是否分析过您的代码并发现这是瓶颈?您可以使用Scanner.nextInt() 节省一些代码行,但不会更快。
  • 也许瓶颈不在这部分代码(这是我的猜测),但我也会尝试优化其他部分的代码
  • 数据集有多大?
  • 你绝对应该先用尽其他可以提高性能的地方。对于 I/O,您无能为力。您可以尝试以某种不同的序列化格式(如 json 或 avro)存储数据,但如果您只需要读取这种格式,则无能为力。另一种选择是尝试通过将其拆分为更多文件并让每个线程处理一个文件来并行化它。
  • 瓶颈(和我的问题的核心)位于我在问题中显示的代码之外的其他地方 - 我错误地将我的问题归类...

标签: java stringtokenizer


【解决方案1】:

(更新答案)

我可以说,无论您的程序速度有什么问题,分词器的选择都不是其中之一。在初始运行每种方法以消除初始化怪癖后,我可以在几毫秒内解析 1000000 行“12 34”。如果您愿意,您可以切换到使用 indexOf,但我真的认为您需要查看其他代码位的瓶颈,而不是这种微优化。拆分对我来说是一个惊喜 - 与其他方法相比,它真的非常慢。我在 Guava 拆分测试中添加了它,它比 String.split 快,但比 StringTokenizer 稍慢。

  • 拆分:371 毫秒
  • IndexOf:48 毫秒
  • StringTokenizer:92 毫秒
  • Guava Splitter.split(): 108ms
  • CsvMapper 构建一个 csv 文档并解析为 POJOS:237 毫秒(如果将行构建到一个文档中,则为 175 毫秒!)

即使超过数百万行,这里的差异也可以忽略不计。

现在我的博客上有一篇关于此的文章:http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/

我运行的代码是:

import java.util.StringTokenizer;
import org.junit.Test;

public class TestSplitter {

private static final String line = "12 34";
private static final int RUNS = 1000000;//000000;

public final void testSplit() {
    long start = System.currentTimeMillis();
    for (int i=0;i<RUNS;i++){
        String[] st = line.split(" ");
        int x = Integer.parseInt(st[0]);
        int y = Integer.parseInt(st[1]);
    }
    System.out.println("Split: "+(System.currentTimeMillis() - start)+"ms");
}

public final void testIndexOf() {
    long start = System.currentTimeMillis();
    for (int i=0;i<RUNS;i++){
        int index = line.indexOf(' ');
        int x = Integer.parseInt(line.substring(0,index));
        int y = Integer.parseInt(line.substring(index+1));
    }       
    System.out.println("IndexOf: "+(System.currentTimeMillis() - start)+"ms");      
}

public final void testTokenizer() {
    long start = System.currentTimeMillis();
    for (int i=0;i<RUNS;i++){
        StringTokenizer st = new StringTokenizer(line, " ");
        int x = Integer.parseInt(st.nextToken());
        int y = Integer.parseInt(st.nextToken());
    }
    System.out.println("StringTokenizer: "+(System.currentTimeMillis() - start)+"ms");
}

@Test
public final void testAll() {
    this.testSplit();
    this.testIndexOf();
    this.testTokenizer();
    this.testSplit();
    this.testIndexOf();
    this.testTokenizer();
}

}

eta:这里是番石榴代码:

public final void testGuavaSplit() {
    long start = System.currentTimeMillis();
    Splitter split = Splitter.on(" ");
    for (int i=0;i<RUNS;i++){
        Iterator<String> it = split.split(line).iterator();
        int x = Integer.parseInt(it.next());
        int y = Integer.parseInt(it.next());
    }
    System.out.println("GuavaSplit: "+(System.currentTimeMillis() - start)+"ms");
}

更新

我也添加了一个 CsvMapper 测试:

public static class CSV{
    public int x;
    public int y;
}

public final void testJacksonSplit() throws JsonProcessingException, IOException {
    CsvMapper mapper = new CsvMapper();
    CsvSchema schema = CsvSchema.builder().addColumn("x", ColumnType.NUMBER).addColumn("y", ColumnType.NUMBER).setColumnSeparator(' ').build();

    long start = System.currentTimeMillis();
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < RUNS; i++) {
        builder.append(line);
        builder.append('\n');
    }       
    String input = builder.toString();
    MappingIterator<CSV> it = mapper.reader(CSV.class).with(schema).readValues(input);
    while (it.hasNext()){
        CSV csv = it.next();
    }
    System.out.println("CsvMapperSplit: " + (System.currentTimeMillis() - start) + "ms");
}

【讨论】:

  • line.split 效率极低 - 它慢了 1.5 倍!
  • 真的吗?这真是一个惊喜。你对它进行过测试吗?我要写一个测试用例。
  • 在 indexOf 方法中可以在单个语句中移动 indexOf 计算吗? int index = line.indexOf(' ');
  • 另一种“疯狂”的解决方案,有一些限制并且没有经过很好的测试:pastebin.com/9Cf2ZVT0
  • 哇。刚刚运行它,它在与其他测试相同的盒子上以 15 毫秒出现!速度提高 3 倍,可读性降低 50 倍:D
【解决方案2】:

您可以使用正则表达式检查值是否为数字,然后转换为整数:

if(st.nextToken().matches("^[0-9]+$"))
        {
           int x = Integer.parseInt(st.nextToken());
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 2014-11-29
    相关资源
    最近更新 更多