优化了很多Scanner.findWithinHorizo​​n(模式,0)调用

时间:2010-06-04 07:16:19

标签: java regex string optimization string-search

我正在构建一个过程,从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令牌之后是相应对象的名称。
  3. 理想情况下,我希望返回一个String,而不是结果的起始位置作为int或其他东西。
  4. 任何可以帮助我的东西,即使它每次搜索节省1ms,也会有所帮助,所以所有输入都值得赞赏。三江源!


    使用场景1:我有一个文件A中的对象列表,旧式系统中的对象编号不在文件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。在另一个进程中注意到的加速比大约是20倍,我编译了[0-9]{1,3}\\.[0-9]%|\r\n|0|[A-Z'-]+,尽管在上面的代码中没有注意到\r\n。沿着这些方向努力,我想知道:

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

    另一种可能的优化是:连接所有标记后,并预先放置(.*)。它会减少大约2/3编译的正则表达式(所有这些都是字面意思),并且还希望允许我从该分组中提取文本而不是保留每行的“潜在令牌”一个ID就可以了。这也值得吗?

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

1 个答案:

答案 0 :(得分:2)

开始时的事情:每次运行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()