【问题标题】:Optimizing a lot of Scanner.findWithinHorizon(pattern, 0) calls优化大量 Scanner.findWithinHorizo​​n(pattern, 0) 调用
【发布时间】:2010-06-05 22:43:26
【问题描述】:

我正在构建一个从 6 个 csv 样式文件和两个布局不佳的 .txt 报告中提取数据并构建输出 CSV 的流程,而且我完全知道在搜索所有数千个空格时会有一些开销次,但我从没想过转换大约 50,000 条记录需要 12 个小时。

我的手动匹配代码摘录(我知道我使用这样的标记列表很糟糕,但这是我能想到的最好的东西):

public static String lookup(Pattern tokenBefore,
                             List<String> tokensAfter)
{
    String result = null;

    while(_match(tokenBefore)) { // block until all input is read
        if(id.hasNext())
        {
            result = id.next(); // capture the  next token that matches

            if(_matchImmediate(tokensAfter)) // try to match tokensAfter to this result
                return result;
        } else
            return null; // end of file; no match
    }

    return null; // no matches
}

private static boolean _match(List<String> tokens)
{
    return _match(tokens, true);
}

private static boolean _match(Pattern token)
{
    if(token != null)
    {
        return (id.findWithinHorizon(token, 0) != null);
    } else {
        return false;
    }
}

private static boolean _match(List<String> tokens, boolean block)
{
    if(tokens != null && !tokens.isEmpty()) {
        if(id.findWithinHorizon(tokens.get(0), 0) == null)
            return false;

        for(int i = 1; i <= tokens.size(); i++)
        {
            if (i == tokens.size()) { // matches all tokens
                return true;
            } else if(id.hasNext() && !id.next().matches(tokens.get(i))) {
                break; // break to blocking behaviour
            }
        }
    } else {
        return true; // empty list always matches
    }

    if(block)
        return _match(tokens); // loop until we find something or nothing
    else
        return false; // return after just one attempted match
}

private static boolean _matchImmediate(List<String> tokens)
{
    if(tokens != null) {

        for(int i = 0; i <= tokens.size(); i++)
        {
            if (i == tokens.size()) { // matches all tokens
                return true;
            } else if(!id.hasNext() || !id.next().matches(tokens.get(i))) {
                return false; // doesn't match, or end of file
            }
        }

        return false; // we have some serious problems if this ever gets called
    } else {
        return true; // empty list always matches
    }
}

基本上想知道我将如何进行有效的字符串搜索(Boyer-Moore 或类似)。我的扫描仪id 正在扫描java.util.String,认为将其缓冲到内存会减少 I/O,因为这里的搜索在相对较小的文件上执行了数千次。与扫描 BufferedReader(FileReader(File)) 相比,性能提升可能不到 1%,这个过程看起来仍然需要很长时间。

我还跟踪了执行情况,我的整个转换过程的缓慢肯定介于查找方法的第一个和最后一个之间。事实上,以至于我运行了一个快捷过程来计算 .csv 样式文件中各种标识符的出现次数(我使用 2 种查找方法,这只是其中一种),并且该过程完成了大约 4 个不同的索引在不到一分钟的时间内识别出 50,000 条记录的标识符。与 12 小时相比,这是瞬间的。

一些说明(2010 年 6 月 6 日更新):

  1. 我仍然需要 tokensBefore 的模式匹配行为。
  2. 我需要的所有 ID 号不一定从一行中的固定位置开始,但保证 ID 标记之后是对应对象的名称。
  3. 理想情况下,我希望返回一个字符串,而不是作为 int 或其他内容的结果的起始位置。

任何可以帮助我的事情,即使每次搜索可以节省 1 毫秒,也会有所帮助,因此感谢所有输入。谢谢!


使用场景 1:我在文件 A 中有一个对象列表,在旧式系统中,这些对象的 ID 号不在文件 A 中。但是,它可能在另一个 csv 样式文件(文件 B ) 或者可能仍然在一个 .txt 报告(文件 C)中,每个报告还包含一堆其他在这里没用的信息,因此需要在文件 B 中搜索对象的全名(1 个标记,因为它会驻留在其中任何给定行的第二列),然后第一列应该是 ID 号。如果这不起作用,那么我们必须在搜索文件 C 以查找这些标记之前,将搜索标记按空格拆分为单独的标记。

通用代码:

String field;
for (/* each record in file A */)
{
    /* construct the rest of this object from file A info */
    // now to find the ID, if we can
    List<String> objectName = new ArrayList<String>(1);
    objectName.add(Pattern.quote(thisObject.fullName));
    field = lookup(objectSearchToken, objectName); // search file B
    if(field == null) // not found in file B
    {
        lookupReset(false); // initialise scanner to check file C
        objectName.clear(); // not using the full name

        String[] tokens = thisObject.fullName.split(id.delimiter().pattern());
        for(String s : tokens)
            objectName.add(Pattern.quote(s));

        field = lookup(objectSearchToken, objectName); // search file C
        lookupReset(true); // back to file B
    } else {
        /* found it, file B specific processing here */
    }

    if(field != null) // found it in B or C
        thisObject.ID = field;
}

objectName 标记都是大写单词,其中可能包含连字符或撇号,以空格(人名)分隔。

根据 aioobe 的回答,我已经为我的常量搜索令牌预编译了正则表达式,在本例中为 \r\n。在我编译 [0-9]{1,3}\\.[0-9]%|\r\n|0|[A-Z'-]+ 的另一个进程中,注意到的加速提升约为 20 倍,尽管在上面带有 \r\n 的代码中没有注意到这一点。沿着这些思路工作,我想知道:

如果唯一可用的匹配项无论如何都在以非空格字符开头的行上,我匹配\r\n[^ ] 会更好吗?它可能会减少 _match 的执行次数。

另一个可能的优化是:连接所有标记之后,并预先放置一个(.*)。它将减少大约 2/3 编译的正则表达式(无论如何都是文字)的数量,并且还希望允许我从该分组中提取文本,而不是从每一行中保留一个“潜在标记”上面有身份证。这也值得吗?

如果我可以让 java.util.Scanner 在调用 findWithinHorizo​​n 后返回当前令牌之前的令牌,则可以解决上述情况。

【问题讨论】:

    标签: java regex string optimization string-search


    【解决方案1】:

    开始:每次运行 id.next().matches(tokens.get(i)) 时,都会执行以下代码:

    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);
    return m.matches();
    

    编译正则表达式并非易事,您应该考虑在程序中一劳永逸地编译模式:

    pattern[i] = Pattern.compile(tokens.get(i));
    

    然后简单地调用类似的东西

    pattern[i].matcher(str).matches()
    

    【讨论】:

    • 好主意。我想这对“任意数量的空白”正则表达式会有所帮助,但由于我的过程主要是从这些文件中提取数据,所以每次调用 lookup() 都有 2-3 个标记,所有这些标记通常都是完全不同的(人们的完整名称,每个令牌一个名称)。我将预编译任何空白正则表达式并重新运行该过程以检查粗略的性能提升。谢谢:)
    • 我想已经完成了。现在使用预编译的“\r\n”模式运行它,处理速度大约为每秒 20 条记录,而不是每秒或每两条记录 1 条记录。仍在寻找其他潜在的优化,但这是一个巨大的帮助,谢谢!
    • 这将我的一个用例从需要 5 小时缩短到大约 15 分钟,但另一个(上面提到的)似乎没有显示出一点变化。可能的原因是如果在文件 B 中找不到该名称的正则表达式,以及所有正在编译的名称,我将其拆分。有关更多信息,请参阅问题。
    猜你喜欢
    • 2014-07-16
    • 2017-05-26
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多