流中两个字符串的正则表达式验证

regex verification of two strings in stream

我有 2 个列表,

  1. 是一个名为 tablerows 的字符串列表。

  2. 是一个对象列表,每个对象都包含一个字符串值列表(除其他外)

我正在尝试通过流迭代它们,如下所示:

tableRows.forEach(row -> objects.forEach(o -> o.getValues().forEach(v ->  {

任务是找到所有值“v”以及它们在哪个表行中使用(如果有),然后调用一个方法来处理表行、其中使用的值和对象它所属的名称。

    if (doesContain(row.getText(), v.getValue())) {
        methodCall....
        }

目前通过正则表达式模式验证

doesContain(String string, String substring) {
        String pattern = "^" + substring+ " \b|\b " + substring+ " \b|\b " + substring+ "$";
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(string);
        return m.find();
}

代码有效,但是 thirst List 包含 3000-6000 个字符串(将来会更多),而第二个列表有大约 100 个对象,每个列表中有大约 20 个字符串来检查......

目前完成 3000 个字符串的列表大约需要 1.3 分钟,而在 1 分钟后会调用超时。

主要的瓶颈似乎是由于使用了模式和匹配器,大约需要 40 秒。 单词边界的使用是必要的,所以我不能使用 equals、contains 或类似的方法。 我的想法是以某种方式直接在流中使用谓词,到目前为止没有成功。

是否可以直接在流中使用正则表达式,同时保留彼此连接的“行”和“v”值,然后为每个成功的对调用方法?

或者是否有更快的替代方法?

编辑:示例: 主要:

public class main {

    public static void main(String[] args) {


        ArrayList<String> tableRows = new ArrayList<>();
        tableRows.add("Text one");
        tableRows.add("Text two");
        tableRows.add("Text three");
        tableRows.add("Text four");
        tableRows.add("Text five");

        ArrayList<String>  valueList = new ArrayList<>();
        valueList.add("one");
        valueList.add("two");
        valueList.add("none");
        valueList.add("four");
        valueList.add("abc");
        ArrayList<testObject> objects = new ArrayList<testObject>();
        objects.add(new testObject("thirstName", valueList));
        objects.add(new testObject("secondName", valueList));

        String replacePattern = Pattern.quote(".") + "|" + Pattern.quote("?") + "|" + Pattern.quote("!") + "|" +
                Pattern.quote(",") + "|" + Pattern.quote(";");
        tableRows.forEach(row -> objects.forEach(o -> o.getValues().forEach(v -> {

            if (doesContain(row, v,replacePattern)) {
                System.out.println("Value :" + v + " of Object " + o.getName() + " is used in row: " + row);
            }
        })));

    }
    private static boolean doesContain(String string, String substring,String replacePattern) {

        string = string.toLowerCase().replaceAll(replacePattern, "");
        substring = substring.toLowerCase();
        String pattern = "^" + substring + " \b|\b " + substring + " \b|\b " + substring + "$";
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(string);
        return m.find();
    }
}

测试对象class:

public class testObject {

    private String name;
    private ArrayList<String> values;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ArrayList<String> getValues() {
        return values;
    }

    public void setValues(ArrayList<String> values) {
        this.values = values;
    }

    public testObject() {
        this.name = "testName";
        this.values = (ArrayList<String>) List.of("one","two");
    }

    public testObject(String name, ArrayList<String> values) {
        this.name = name;
        this.values = values;
    }
}

示例输出:

Value:  one  of Object: thirstName is used in row: Text one
Value:  one  of Object: secondName is used in row: Text one
Value:  two  of Object: thirstName is used in row: Text two
Value:  two  of Object: secondName is used in row: Text two
Value:  four  of Object: thirstName is used in row: Text four
Value:  four  of Object: secondName is used in row: Text four

当您想确保匹配的是整个单词时,您应该简单地将单词边界锚点添加到单词中,而不是添加空格并尝试将锚点应用于前一个单词和下一个单词。这简化了匹配 word 但不匹配 words"\bword\b" 的模式。此外,您可以将列表中的所有单词组合成一个模式,如 "\b(one|two|none|four|abc)\b" 以搜索这些单词中的任何一个的第一次出现。这消除了迭代单词的需要。但如果字符串中出现多个这些词,它可能会报告不同的词。

当您将迭代逻辑更改为 objects.forEach(o -> tableRows.forEach(row -> …)) 时,您只需要构建每个模式一次,而不是对每一行重复构建。但这当然会以不同的顺序打印结果。

然后,避免将所有字符串转换为大写或小写以有效执行不区分大小写的搜索的普遍反模式。 Java 有专门的不区分大小写搜索的方法,我不知道为什么这么多开发人员忽略它们而宁愿执行昂贵的转换。请注意,搜索可以在第一次出现时停止,而 toLowercase() 转换必须处理整个字符串,甚至可能在搜索开始之前生成一个新字符串。

把这些点放在一起,你可以这样操作:

objects.forEach(o -> {
    Pattern p = Pattern.compile(
        o.getValues().stream().collect(Collectors.joining("|", "\b(", ")\b")),
        Pattern.CASE_INSENSITIVE);
    tableRows.forEach(row -> {
        Matcher m = p.matcher(row);
        if(m.find()) {
            System.out.println("Value: " + m.group()
                + " of Object " + o.getName() + " is used in row: " + row);
        }
    });
});

这是从 getValues() 为每个 testObject 实例返回的列表构建组合模式。由于模式可能匹配这些词中的任何一个,我们必须查询匹配器 (m.group()) 以找出我们找到的是哪个词。请注意,可能会出现更多情况。此匹配操作在字符串中找到的第一个单词处停止,而您的原始代码在已找到的列表的第一个单词处停止,即使它在字符串中出现的时间晚于列表中出现的另一个单词。

如果您多次执行此操作,您甚至可以将模式存储在 testObject 本身中,例如

public class TestObject {
    private String name;
    private List<String> values;
    private Pattern pattern;

    public TestObject() {
        this("testName", List.of("one","two"));
    }

    public TestObject(String name, List<String> values) {
        this.name = name;
        setValues(values);
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<String> getValues() {
        return values;
    }
    public void setValues(List<String> values) {
        this.values = List.copyOf(values);
        pattern = Pattern.compile(
            this.values.stream().collect(Collectors.joining("|", "\b(", ")\b")),
            Pattern.CASE_INSENSITIVE);
    }
    public Pattern getPattern() {
        return pattern;
    }
}

我借此机会解决了此 class 的其他一些问题,例如不假设每个 List 都是 ArrayList。它还始终使用不可变列表作为其内部存储,以确保没有相应的 Pattern 更新,任何人都无法更改它。

除了保留不同匹配操作之间的模式外,这还允许按照与原始代码相同的顺序检查对象,例如

tableRows.forEach(row -> objects.forEach(o -> {
    Matcher m = o.getPattern().matcher(row);
    if(m.find()) {
        System.out.println("Value: " + m.group()
            + " of Object " + o.getName() + " is used in row: " + row);
    }
}));