在字符串上运行多个正则表达式模式

4
假设我有一个 List<String> 和一个空的 List<Pattern>,以下是将 String 中的单词转换为 Pattern 对象的最佳方法:
for(String word : stringList) {
    patterns.add(Pattern.compile("\\b(" + word + ")\\b);
}

然后稍后运行此字符串:

for(Pattern pattern : patterns) {
    Matcher matcher = pattern.matcher(myString);
    if(matcher.matches()) {
         myString = matcher.replaceAll("String[$1]");
    }
}

replaceAll只是一个例子,但在我使用它时,$1将被大多数使用。

有没有更有效的方法?因为我觉得这有点笨重。顺便说一下,我正在使用80个字符串列表,虽然使用的字符串是可配置的,所以不会总是有这么多。

这是设计成一种骂人过滤器,所以我会让你假设列表中的单词,

输入的一个例子是"You're a <curse>",输出将是"You're a *****",尽管这可能并不总是这样,而且在某些时候我可能会从HashMap<String, String>读取,其中键是捕获组,值是替换。

示例:

if(hashMap.get(matcher.group(1)) == null) { 
    // Can't test if \ is required. Used it here for safe measure.
    matcher.replaceAll("\*\*\*\*");
 } else {
    matcher.replaceAll(hashMap.get(matcher.group(1));
 }

@RealSkeptic,我在开头和结尾加了\b来解决那个问题,鉴于\b是用于单词边界的,这样做不会修复你提到的问题吗? - Connor Spencer Harries
@AvinashRaj,已经更新并加入一个例子。 - Connor Spencer Harries
没错,在这种情况下 \\b 是行不通的。如果在开头和结尾都不加 \\b 会有什么问题? - Avinash Raj
@AvinashRaj,嗯,我一直知道\b标记单词边界,这只是一个单词过滤器,所以你可以看到我想从哪里来 :) - Connor Spencer Harries
谢谢@vsb,我想我会坚持这个,因为它不需要像你说的那样复杂 :P - Connor Spencer Harries
显示剩余5条评论
3个回答

4

您可以使用竖线符号|将这些模式组合在一起:

Pattern pattern = Pattern.compile("\\b(" + String.join("|",stringList) + ")\\b");

如果您无法使用Java 8,那么就没有String.join方法可用。或者,如果需要转义单词以防止其中的字符被解释为正则表达式元字符,则需要使用手动循环构建此正则表达式。请参考以下内容:

String.join 方法和quote方法

StringBuilder regex = new StringBuilder("\\b(");
for (String word : stringList) {
    regex.append(Pattern.quote(word));
    regex.append("|");
}
regex.setLength(regex.length() - 1); // delete last added "|"
regex.append(")\\b");
Pattern pattern = Pattern.compile(regex.toString());

为了针对不同的单词使用不同的替换方案,您可以使用以下循环应用模式:
Matcher m = pattern.matcher(myString);
StringBuilder out = new StringBuilder();
int pos = 0;
while (m.find()) {
    out.append(myString, pos, m.start());
    String matchedWord = m.group(1);
    String replacement = matchedWord.replaceAll(".", "*");
    out.append(replacement);
    pos = m.end();
}
out.append(myString, pos, myString.length());
myString = out.toString();

您可以按照您喜欢的任何方式查找匹配单词的替换。该示例生成一个与匹配单词长度相同的星号替换字符串。

请看我所做的编辑以使自己更清晰,但感谢StringBuilder#setLength,不知道StringBuilder有这个功能!对于任何没有使用Java 8的人,还有Google的Joiner,我知道它可能过度了,但它可以帮助那些不知道它的人。 - Connor Spencer Harries

2
Boann的想法已经很好了。但是,例如对于日志过滤,我有一个大型的过滤器列表,其中文本与正则表达式匹配,我需要知道哪个过滤器匹配。因此,我将其他过滤器(如模块、代码、级别等)也编码为正则表达式。如果有匹配项,我会检查哪个组匹配。
1)因此,每行只检查一次。
2)由于所有正则表达式都构建到一个匹配器中,因此每个字符只检查一次。
这是从N(条件数量)到几乎1(几乎任何数量的过滤器的常数)的极大改进。
public static void main(final String[] argc) throws Throwable {
    Config c;
    try(InputStream s = new FileInputStream("webapp/WEB-INF/logScanConfig.xml")) { c = (Config) JAXBContext.newInstance(Config.class).createUnmarshaller().unmarshal(s); }
    final LineContext[] a = c.rules.toArray(new LineContext[c.rules.size()]);
    final StringBuilder regex = new StringBuilder();
    for(int i=0;i<a.length;i++) {
        final LineContext e = a[i];
        final String p ="(^"+
                (e.modul == null?".*":e.modul)+" ; "+
                (e.code  == null?".*":e.code )+" ; "+
                (e.mesg  == null?".*":e.mesg )+" ; "+
                (e.level == null?".*":e.level)+" ; "+
                (e.regex == null?".*":e.regex)+"$)";
        if(regex.length()>0) regex.append("|");
        regex.append(p);
    }

    final Pattern pattern = Pattern.compile(regex.toString(), Pattern.DOTALL);
    final Matcher m = pattern.matcher("ISS ; 0025 ; 0008 ; I ; State Manager started");
    if(!m.matches()) {
        System.out.println("Not Found");
    } else {
        System.out.println("GroupCount: "+m.groupCount()+" A["+a.length+"]");
        for(int i=1;i<=m.groupCount();i++) {
            if(null != m.group(i)) {
                System.out.println("GROUP["+(i-1)+"]: "+m.group(i));
                System.out.println(a[i-1]);
            }
        }
    }
  }
}

这里是logScanConfig.xml的一个示例

<logScanConfig user="private.1" pass="private.2">
 <logUrls>
  <e>http://private.3:80/fetch/log</e>
  <e>http://private.4:80/fetch/log</e>
  <e>http://private.5:80/fetch/log</e>
 </logUrls>
 <rules>
  <e backlogTime='600' minCount='0' maxCount='0' modul='ART' code='0114' mesg='1007' level='E'><regex>.*ORA-27101: shared memory realm does not exist.*</regex></e>
  <e backlogTime='600' minCount='0' maxCount='0' modul='ISS' code='0098'             level='E'><regex>Insufficient memory .*</regex></e>
 </rules>
</logScanConfig>

你能否提供一个logscanConfig.xml的示例? - Grinish Nepal

1

如果无论匹配什么单词,你都要执行相同的操作,可以从这些单词中组成一个大的“OR”表达式,并使用单个模式,如下所示:

\\b(<word1>|<word2>|...|<wordN>)\\b

在循环中,应将<wordK>替换为您的单词:

StringBuilder res = new StringBuilder("\\b(");
boolean first = true;
for(String word : stringList) {
    if (!first) {
        res.append("|");
    } else {
        first = false;
    }
    res.append(word);
}
res.append(")\\b");
Pattern p = Pattern.compile(res.toString());

注意:本解决方案假设单词不包含正则表达式元字符。

谢谢您的回复,请问您能否看一下我的编辑呢? - Connor Spencer Harries
@charries96 编辑方面怎么样?这个与你的代码兼容,但只用了一个表达式。 - Sergey Kalinichenko
没事了,以为它改变了什么,但我猜它没有。早上有点迷糊。 - Connor Spencer Harries

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接