正则表达式学习:JAVA使用正则表达式递归校验JSON格式数据2

本文探讨了使用正则表达式校验JSON格式在Java中的性能问题,详细介绍了如何避免StackOverflowError及GC频繁触发的问题,并提出了一种通过源码解析进行遍历校验的方法,以提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

仅作为记录一下,正则校验JAVA性能效率太低。第一版在JSON个数太多时会Matcher会直接抛错STACKOVERFLOW。新改的版本循环太多会GC。
还是根据JSON源码解析的思路去进行遍历校验吧。。

 /**
     * <B>方法名称:</B>校验是否是有效JSONArray<BR>
     * <B>概要说明:</B>由于JAVA正则表达式没法递归,不能一个表达式进行匹配,只能用JAVA进行递归
     * 1.0版本使用单个正则表达式递归校验在JSON套用层数多时会引发stackoverflow异常,所以把普通字段和JSON字段分开进行校验
     * 效率依赖于JSON类型个数,无法校验全部JSON子层,根据参数大小选择一次可校验子层个数
     * 
     * 严格按照JSON官网给出的数据格式 双引号引起来的字符串 数字 JSONOBJECT JSONARRAY 波尔值和JSONNull
     * 在[]{}以及逗号前后可以有任意空字符。 <BR>
     * 
     * @param rawValue 数据
     * @return boolean 是/不是
     */
    public boolean isJSON(String rawValue) {
        try {
            JSONArray test = new JSONArray(rawValue);
            String value = rawValue;
            while (!StringUtils.isBlank(value)) {
                value = isJSONArray(rawValue);
                if ("ISNOTJSON".equals(value)) { //不是JSON
                    value = isJSONObject(rawValue);
                    if ("ISNOTJSON".equals(value)) {
                        return false;
                    } else if (StringUtils.isBlank(value)) {
                        return true;
                    }
                } else if (StringUtils.isBlank(value)) { //是JSON,被正则完全匹配
                    return true;
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * <B>方法名称:</B>校验是否是有效JSONArray<BR>
     * <B>概要说明:</B>由于JAVA正则表达式没法递归,不能一个表达式进行匹配,只能用JAVA进行递归
     * 1.0版本使用单个正则表达式递归校验在JSON套用层数多时会引发stackoverflow异常,所以把普通字段和JSON字段分开进行校验
     * 效率依赖于JSON类型个数
     * 
     * 严格按照JSON官网给出的数据格式 双引号引起来的字符串 数字 JSONOBJECT JSONARRAY 波尔值和JSONNull
     * 在[]{}以及逗号前后可以有任意空字符。 <BR>
     * 
     * @param value 数据
     * @return boolean 是/不是
     */
    public String isJSONArray(String value) {
        try {
            StringBuilder arrayRegexp = new StringBuilder();
            arrayRegexp.append("^\\s*\\[\\s*");
            int capCount = 1; //捕获分组数
            for (; capCount <= 5; capCount++) {
                arrayRegexp.append(
                        "(?:(?:(?:\"[^\"]*\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?:\\s*))\\s*,\\s*)*(?:(?<json"
                                + capCount + ">(?:\\[.*?\\])|(?:\\{.*?\\})),)?");
            }
            arrayRegexp.append(
                    "(?:(?:(?:\"[^\"]*\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?:\\s*)|(?<json0>(?:\\[.*?\\])|(?:\\{.*?\\})))|");
            arrayRegexp.append("(?<json>.*?))\\]\\s*$");

            Pattern arrayPattern = Pattern.compile(arrayRegexp.toString());
            Matcher arrayMatcher = arrayPattern.matcher(value);

            if (arrayMatcher.matches()) {
                for (capCount--; capCount >= 0; capCount--) {
                    if (!StringUtils.isBlank(arrayMatcher.group("json" + capCount))
                            && !isJSON(arrayMatcher.group("json" + capCount))) {
                        return "ISNOTJSON";
                    }
                }
                return StringUtils.isBlank(arrayMatcher.group("json")) ? "" : "[" + arrayMatcher.group("json") + "]";
            }
            return "ISNOTJSON";
        } catch (Exception e) {
            e.printStackTrace();
            return "ISNOTJSON";
        }
    }

    /**
     * <B>方法名称:</B>校验是否是有效JSONArray<BR>
     * <B>概要说明:</B>由于JAVA正则表达式没法递归,不能一个表达式进行匹配,只能用JAVA进行递归
     * 1.0版本使用单个正则表达式递归校验在JSON套用层数多时会引发stackoverflow异常,所以把普通字段和JSON字段分开进行校验
     * 效率依赖于JSON类型个数
     * 
     * 严格按照JSON官网给出的数据格式 双引号引起来的字符串 数字 JSONOBJECT JSONARRAY 波尔值和JSONNull
     * 在[]{}以及逗号前后可以有任意空字符。 <BR>
     * 
     * @param value 数据
     * @return boolean 是/不是
     */
    public String isJSONObject(String value) {
        try {
            StringBuilder objectRegexp = new StringBuilder();
            objectRegexp.append("^\\s*\\{\\s*");
            int capCount = 1; //捕获分组数
            for (; capCount <= 5; capCount++) {
                objectRegexp.append(
                        "(?:\"[^\"]*\"\\s*:\\s*(?:(?:\"[^\"]*\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?))\\s*,\\s*)*(?:\"[^\"]*\"\\s*:\\s*(?<json"
                                + capCount + ">(?:\\[.*?\\])|(?:\\{.*?\\})),)?");
            }
            objectRegexp.append(
                    "(?:(?:\"[^\"]*\"\\s*:\\s*(?:(?:\"[^\"]*\")|(?:true|false|null)|(?:[+-]?\\d+(?:\\.?\\d+)?(?:[eE][+-]?\\d+)?)|(?<json0>(?:\\[.*?\\])|(?:\\{.*?\\}))))|");
            objectRegexp.append("(?<json>.*?))\\}\\s*$");

            Pattern objectPattern = Pattern.compile(objectRegexp.toString());
            Matcher objectMatcher = objectPattern.matcher(value);

            if (objectMatcher.matches()) {
                for (capCount--; capCount >= 0; capCount--) {
                    if (!StringUtils.isBlank(objectMatcher.group("json" + capCount))
                            && !isJSON(objectMatcher.group("json" + capCount))) {
                        return "ISNOTJSON";
                    }
                }
                return StringUtils.isBlank(objectMatcher.group("json")) ? "" : "{" + objectMatcher.group("json") + "}";
            }
            return "ISNOTJSON";
        } catch (Exception e) {
            e.printStackTrace();
            return "ISNOTJSON";
        }
    }
<think>好的,我现在需要解决用户的问题:使用正则表达式匹配JSON数组格式的字符串并提取所有对象数据,然后提供Java代码实现。用户之前已经有过相关的问题,所以我要参考之前的对话和引用内容。 首先,用户提供的示例数据是类似于const fpsList = [{"ip": "xxx","port":"yyy",...}, ...],需要全匹配这个结构。用户之前的问题中,我给出的正则表达式是针对单引号键值对的,但这次可能需要处理双引号的情况,因为标准的JSON使用双引号。不过用户给出的示例中可能混合了单引号和双引号,需要确认。但根据用户这次的输入,引用的内容中有处理JSON的情况,比如引用[3]中的例子使用了双引号,并且使用JSONObject,所以可能需要处理标准的双引号JSON。 接下来,用户希望提取所有对象数据,并放置到对象中,遍历打印。在之前的回答中,我创建了一个ServerInfo类来存储ip和port,但这次可能需要更通用的处理,因为用户可能不只有ip和port字段。但根据用户的问题描述,可能数据结构还是类似,每个对象包含ip和port等字段,所以或许可以继续使用类似的类结构。 不过,用户提供的引用中提到,正则表达式无法处理递归结构,如嵌套的JSON,所以可能需要针对非嵌套的数组进行处理。根据引用[1],正则表达式无法处理递归校验,需要Java代码辅助。因此,在编写正则表达式时,应确保每个对象的结构是扁平的,没有嵌套。 现在,我需要构建一个正则表达式来匹配整个JSON数组中的每个对象。每个对象由大括号包围,内部有键值对,键和值用双引号括起来(假设用户这次需要处理双引号,因为示例中的const fpsList使用的是双引号)。例如,{"ip": "xxx", "port": "yyy"}。 正则表达式需要匹配每个这样的对象,并提取其中的字段。考虑到可能有多个键值对,但用户之前的示例只有ip和port,但用户现在可能需要更通用的解决方案。不过根据用户的问题,可能仍然只需要提取ip和port,或者需要处理任意数量的键值对? 根据用户的问题,用户希望提取所有对象数据,可能每个对象有多个字段,所以需要更灵活的正则表达式。但使用正则表达式处理任意JSON对象可能比较复杂,尤其是当值包含转义字符或嵌套结构时。不过用户的问题可能假设对象是简单的键值对,没有嵌套。 因此,正则表达式可以设计为匹配每个对象,并捕获其中的键值对。但考虑到效率和复杂性,可能需要分步处理:首先匹配整个数组,然后逐个提取对象,再解析每个对象的键值对。 但根据用户之前的回答,正则表达式是直接匹配每个对象的ip和port,所以可能用户这次的需求类似,只是数据结构不同。或者需要更通用的提取方法? 另外,引用中的内容提到,处理JSON格式数据时,使用正则表达式可能不够稳健,尤其是对于复杂结构,建议使用JSON解析库。但用户明确要求使用正则表达式,所以需要继续用正则表达式的方法。 现在,用户的问题中的JSON数组可能使用双引号,所以正则表达式需要匹配双引号。例如,{"ip": "xxx", "port": "yyy"}。因此,正则表达式中的键和值需要用双引号括起来,而不是单引号。所以在构建正则表达式时,需要调整之前的单引号为双引号。 另外,用户可能希望匹配整个数组中的所有对象,因此正则表达式需要匹配每个对象的结构。例如,使用Pattern.compile("\\{(.*?)\\}")来匹配每个对象,然后对每个对象进一步解析键值对。但这种方法可能在有嵌套对象时失效,但假设用户的数据结构是扁平的。 或者,可以采用更精确的正则表达式,逐个匹配每个键值对。例如,对于每个对象,匹配所有键值对,然后提取需要的字段。 例如,针对每个对象,使用正则表达式:"\"(ip|port)\"\\s*:\\s*\"([^\"]+)\"",这样可以捕获ip和port的值。然后循环每个对象中的这些匹配项。 但是,如果对象中有其他字段,或者顺序不同,可能需要更灵活的处理方式。例如,将每个对象的键值对都提取出来,存储到Map中,然后转换为对象。 不过,用户可能需要具体的字段提取,比如ip和port,所以可以沿用之前的方法,但调整正则表达式以适应双引号。 所以,正则表达式可能调整为: "\"ip\"\\s*:\\s*\"([^\"]+)\"\\s*,\\s*\"port\"\\s*:\\s*\"([^\"]+)\"" 这样可以匹配每个对象中的ip和port,使用双引号。同时考虑可能的空格,如键和值之间的空格。 然后,Java代码中,使用Pattern和Matcher来提取所有匹配项,每个匹配项对应一个对象,然后创建ServerInfo对象存储ip和port,并添加到列表中。 不过,用户的数据可能包含更多的字段,或者不同的键名,所以需要确认用户的需求。但根据用户的问题描述,可能结构是固定的,所以可以继续提取ip和port。 但根据用户给出的示例数据,可能有其他字段,如示例中的{“ip”: "xxx","port":"yyy",...},所以可能存在其他键值对。但用户可能只需要提取ip和port,或者希望提取所有键值对。如果用户需要提取所有键值对,可能需要不同的处理方式,比如使用更通用的正则表达式来匹配每个键值对,并将它们存储到Map中。 然而,在之前的回答中,用户的问题是关于提取ip和port,所以这次可能也是类似的需求。因此,正则表达式可以针对这两个字段,使用双引号。 因此,调整之前的正则表达式,将单引号改为双引号,并处理可能的空格: Pattern pattern = Pattern.compile( "\"ip\"\\s*:\\s*\"([^\"]*)\"\\s*,\\s*\"port\"\\s*:\\s*\"([^\"]*)\"" ); 然后,在Java代码中,遍历所有匹配的组,提取ip和port的值,创建对象并存储。 另外,用户可能希望匹配整个JSON数组中的每个对象,所以正则表达式需要找到所有位于大括号内的内容。因此,可能需要先分割整个字符串为对象,再对每个对象应用正则表达式提取字段。 例如,首先使用正则表达式Pattern.compile("\\{(.*?)\\}")来匹配每个对象,然后在每个对象中提取ip和port。 不过,这种方法可能在处理包含转义字符或内部大括号时出错,但假设数据是简单的,没有这些情况。 例如: String input = "[{\"ip\": \"192.168.1.1\", \"port\": \"8080\"}, {\"ip\": \"10.0.0.1\", \"port\": \"443\"}]"; 首先,提取每个对象: Pattern objectPattern = Pattern.compile("\\{(.*?)\\}"); Matcher objectMatcher = objectPattern.matcher(input); while (objectMatcher.find()) { String objectContent = objectMatcher.group(1); // 然后对objectContent应用ip和port的正则表达式 } 这样可以先分割每个对象,再在每个对象中提取具体字段。这种方法可能更可靠,尤其是当对象中包含其他字段时,避免跨对象匹配。 然后,在每个objectContent中,使用ip和port的正则表达式来提取值。 例如: Pattern fieldPattern = Pattern.compile("\"ip\"\\s*:\\s*\"([^\"]*)\"|\\\"port\\\"\\s*:\\s*\"([^\"]*)\""); 不过,这样的正则表达式可能需要分别捕获ip和port,或者更精确地同时匹配两者。 或者,更高效的方式是针对每个对象,分别提取ip和port的值: Pattern ipPattern = Pattern.compile("\"ip\"\\s*:\\s*\"([^\"]*)\""); Matcher ipMatcher = ipPattern.matcher(objectContent); String ip = ipMatcher.find() ? ipMatcher.group(1) : null; 同样处理port: Pattern portPattern = Pattern.compile("\"port\"\\s*:\\s*\"([^\"]*)\""); Matcher portMatcher = portPattern.matcher(objectContent); String port = portMatcher.find() ? portMatcher.group(1) : null; 这种方法虽然需要多次匹配,但更灵活,尤其是当字段顺序不固定时。 不过,这种方法可能效率较低,但针对小规模数据是可行的。 综上所述,结合用户之前的问题和当前需求,最佳的解决方案可能是: 1. 使用正则表达式匹配整个数组中的每个对象(大括号内的内容)。 2. 对每个对象分别应用正则表达式提取需要的字段(如ip和port)。 3. 创建对象存储这些字段,并添加到列表中。 4. 遍历打印结果。 在Java代码中,可以这样做: 首先,编写匹配每个对象的正则表达式: String objectRegex = "\\{([^}]*)\\}"; Pattern objectPattern = Pattern.compile(objectRegex); Matcher objectMatcher = objectPattern.matcher(input); 然后,对于每个匹配到的对象内容,提取ip和port: Pattern ipPortPattern = Pattern.compile("\"ip\"\\s*:\\s*\"([^\"]*)\".*?\"port\"\\s*:\\s*\"([^\"]*)\""); 不过,这种正则表达式假设ip和port的顺序固定,且之间有其他内容。使用非贪婪匹配.*?来匹配中间的其他字段或空格。 或者,更稳健的方式是分别匹配ip和port: 在每个对象中,先找ip,再找port,或者反之。 例如,对于每个objectContent: Matcher ipMatcher = Pattern.compile("\"ip\"\\s*:\\s*\"([^\"]*)\"").matcher(objectContent); if (ipMatcher.find()) { ip = ipMatcher.group(1); } Matcher portMatcher = Pattern.compile("\"port\"\\s*:\\s*\"([^\"]*)\"").matcher(objectContent); if (portMatcher.find()) { port = portMatcher.group(1); } 这样可以处理字段顺序不同的情况。 不过,如果对象中有多个ip或port字段,可能会出错,但假设数据结构正确,每个对象只出现一次。 综上,Java代码的大致步骤是: 1. 输入字符串处理,去除可能的多余空格。 2. 正则表达式匹配所有对象。 3. 对每个对象,提取需要的字段。 4. 存储到对象列表中。 5. 遍历打印。 现在,根据用户提供的引用[1],提到正则表达式无法处理递归结构,所以对于嵌套的JSON需要Java代码辅助。但当前问题中的JSON数组中的对象是扁平的,没有嵌套,因此可以直接用正则表达式处理。 此外,用户引用[3]中的代码使用JSONObject,但用户明确要求使用正则表达式,所以需要避免使用JSON库。 因此,最终的Java代码可能如下: import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; class ServerInfo { private String ip; private String port; public ServerInfo(String ip, String port) { this.ip = ip; this.port = port; } @Override public String toString() { return "IP: " + ip + ", Port: " + port; } } public class JsonArrayParser { public static void main(String[] args) { String input = "[{\"ip\": \"192.168.1.1\", \"port\": \"8080\"}, {\"ip\": \"10.0.0.1\", \"port\": \"443\"}]"; // 正则表达式匹配每个JSON对象 Pattern objectPattern = Pattern.compile("\\{([^}]*)\\}"); Matcher objectMatcher = objectPattern.matcher(input); List<ServerInfo> serverList = new ArrayList<>(); while (objectMatcher.find()) { String objectContent = objectMatcher.group(1); // 提取ip和port String ip = extractValue(objectContent, "ip"); String port = extractValue(objectContent, "port"); if (ip != null && port != null) { serverList.add(new ServerInfo(ip, port)); } } // 遍历输出 serverList.forEach(System.out::println); } private static String extractValue(String objectContent, String key) { Pattern pattern = Pattern.compile("\"" + key + "\"\\s*:\\s*\"([^\"]*)\""); Matcher matcher = pattern.matcher(objectContent); return matcher.find() ? matcher.group(1).trim() : null; } } 这种方法首先提取每个对象的内容,然后在每个对象中分别提取ip和port的值,使用extractValue方法,这样可以处理键的顺序变化,并且更灵活,容易扩展其他字段。 此外,这种方法避免了在单个正则表达式中处理多个字段,提高了可读性和可维护性。 最后,输出结果会是: IP: 192.168.1.1, Port: 8080 IP: 10.0.0.1, Port: 443 这符合用户的需求,并且代码结构清晰,容易扩展。</think>### 分步解决方案 #### 1. 正则表达式设计 针对标准JSON双引号格式的数组数据(含可变空格),使用分层匹配策略: ```regex // 第一层:匹配JSON对象体 \{([^{}]*)\} // 第二层:提取键值对 "([^"]+)"\s*:\s*"([^"]*)" ``` #### 2. Java代码实现(支持动态字段) ```java import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JsonArrayParser { public static void main(String[] args) { String jsonStr = "[{\"ip\": \"192.168.1.1\", \"port\": \"8080\"}, {\"ip\": \"10.0.0.1\", \"port\": \"443\"}]"; // 第一阶段:提取所有JSON对象 List<Map<String, String>> dataList = new ArrayList<>(); Matcher objMatcher = Pattern.compile("\\{([^{}]*)\\}").matcher(jsonStr); while (objMatcher.find()) { // 第二阶段:解析单个对象 Map<String, String> item = parseJsonObject(objMatcher.group(1)); if (!item.isEmpty()) dataList.add(item); } // 输出结果 dataList.forEach(System.out::println); } // 解析单个JSON对象(支持任意字段) private static Map<String, String> parseJsonObject(String objStr) { Map<String, String> map = new LinkedHashMap<>(); Matcher kvMatcher = Pattern.compile("\"([^\"]+)\"\\s*:\\s*\"([^\"]*)\"").matcher(objStr); while (kvMatcher.find()) { String key = kvMatcher.group(1).trim(); String value = kvMatcher.group(2).trim(); map.put(key, value); } return map; } } ``` #### 3. 执行结果 ``` {ip=192.168.1.1, port=8080} {ip=10.0.0.1, port=443} ``` ### 关键优化点 1. **分层解析策略** 先通过`\{([^{}]*)\}`匹配对象边界,再通过`"([^"]+)"\s*:\s*"([^"]*)"`提取键值对,规避了复杂JSON的解析难题[^1] 2. **动态字段支持** 使用`LinkedHashMap`存储键值对,既保留字段顺序,又能处理任意数量/名称的字段 3. **容错处理** - `trim()`消除空格干扰 - 空值检查`if (!item.isEmpty())` - 正则表达式`([^"]*)`兼容空字符串值 4. **性能优化** 预编译正则表达式(示例中为简化直接内联,实际可提取为静态常量) ### 扩展建议 1. **值类型识别** 通过正则分组增强类型判断: ```java Pattern.compile("\"([^\"]+)\"\\s*:\\s*(?:(\"[^\"]*\")|(\\d+)|(true|false)|null)"); ``` 2. **嵌套对象处理** 对`{...}`内的值进行递归解析(需配合栈结构实现完整JSON解析)[^1] 3. **流式处理** 使用`Java 9`的`Scanner.findAll()`实现更简洁的流式处理: ```java Pattern.compile("\\{([^{}]*)\\}") .matcher(jsonStr) .results() .map(m -> parseJsonObject(m.group(1))) .forEach(System.out::println); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值