0

我正在构建一个从 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 或类似)。我的 Scannerid正在扫描一个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. 在我编译的另一个进程中,注意到的加速大约是 20 倍[0-9]{1,3}\\.[0-9]%|\r\n|0|[A-Z'-]+,尽管在上面的代码中没有注意到\r\n. 沿着这些思路工作,我想知道:

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

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

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

4

1 回答 1

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()
于 2010-06-04T07:44:09.057 回答