解析自定义数据

indexOf + substring

java 复制代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 高性能自定义格式解析器(无正则表达式)
 * - 支持任意键名,自动识别 "-key" 后缀进行列表项属性映射
 * - 去除 key 和 value 的首尾空格,但 value 内部空格保留
 * - 键值对分隔符为 ":= ",列表使用 ":[" ... "]" 包围,列表项使用 "{" ... "}"
 * - 所有预定义分隔符(:=, :[, :], :{, :}, :,, :|)前后均可出现空格,解析时自动忽略
 * - 提供完整的格式校验,非法输入会抛出 IllegalArgumentException
 */
public class CustomParser {
    private final String input;
    private int pos;
    private final int len;

    public CustomParser(String input) {
        this.input = input;
        this.pos = 0;
        this.len = input.length();
    }

    // 跳过当前位置的空白字符
    private void skipSpaces() {
        while (pos < len && Character.isWhitespace(input.charAt(pos))) {
            pos++;
        }
    }

    // 断言当前位置为指定字符串,否则抛出异常
    private void expect(String expected) {
        skipSpaces();
        if (!input.startsWith(expected, pos)) {
            throw new IllegalArgumentException(
                String.format("Expected '%s' at position %d, but found '%s'",
                              expected, pos, pos < len ? input.substring(pos, Math.min(pos + 10, len)) : "<EOF>")
            );
        }
        pos += expected.length();
    }

    // 检查是否到达结尾或只有空白
    private boolean isEndOrWhitespace() {
        skipSpaces();
        return pos >= len;
    }

    public Map<String, Object> parse() {
        Map<String, Object> result = new LinkedHashMap<>();

        while (pos < len) {
            skipSpaces();
            if (pos >= len) break;

            // 查找键值分隔符 :=
            int eqIdx = input.indexOf(":=", pos);
            if (eqIdx < 0) {
                throw new IllegalArgumentException("Missing ':=' at position " + pos);
            }
            // 提取 key 并去除首尾空格,不允许为空
            String key = input.substring(pos, eqIdx).trim();
            if (key.isEmpty()) {
                throw new IllegalArgumentException("Empty key at position " + pos);
            }
            pos = eqIdx + 2;

            // 解析值(简单值或列表)
            Object value = parseValue();
            result.put(key, value);

            // 跳过键值对分隔符 :|(允许前后空格)
            skipSpaces();
            if (pos < len) {
                if (input.startsWith(":|", pos)) {
                    pos += 2;
                } else {
                    // 如果还有剩余字符,但不是 :|,则视为非法
                    throw new IllegalArgumentException(
                        String.format("Expected ':|' at position %d, but found '%s'",
                                      pos, pos < len ? input.substring(pos, Math.min(pos + 10, len)) : "<EOF>")
                    );
                }
            }
        }

        // ========== 后处理:将所有 "-key" 后缀的键与其前缀进行映射 ==========
        List<String> keySuffixKeys = new ArrayList<>();
        for (String k : result.keySet()) {
            if (k.endsWith("-key")) {
                keySuffixKeys.add(k);
            }
        }

        for (String keyWithSuffix : keySuffixKeys) {
            String prefix = keyWithSuffix.substring(0, keyWithSuffix.length() - 4);
            Object prefixValue = result.get(prefix);
            if (prefixValue instanceof List) {
                List<?> rawList = (List<?>) prefixValue;
                if (!rawList.isEmpty() && rawList.get(0) instanceof List) {
                    Object keyObj = result.get(keyWithSuffix);
                    if (!(keyObj instanceof List)) {
                        throw new IllegalArgumentException("Invalid format for key list: " + keyWithSuffix);
                    }
                    List<?> keyList = (List<?>) keyObj;
                    List<String> fieldNames;
                    if (keyList.isEmpty()) {
                        fieldNames = Collections.emptyList();
                    } else if (keyList.get(0) instanceof List) {
                        // 只取第一个列表作为字段名定义(规范应只有一项)
                        fieldNames = (List<String>) keyList.get(0);
                    } else {
                        throw new IllegalArgumentException("Invalid field name list in " + keyWithSuffix);
                    }

                    List<Map<String, Object>> mappedList = new ArrayList<>();
                    for (Object item : rawList) {
                        if (!(item instanceof List)) {
                            throw new IllegalArgumentException("Invalid list item format for " + prefix);
                        }
                        List<?> itemValues = (List<?>) item;
                        if (fieldNames.size() != itemValues.size()) {
                            throw new IllegalArgumentException(
                                String.format("Value count mismatch for key '%s': expected %d fields but got %d",
                                    prefix, fieldNames.size(), itemValues.size())
                            );
                        }
                        Map<String, Object> map = new LinkedHashMap<>();
                        for (int i = 0; i < fieldNames.size(); i++) {
                            map.put(fieldNames.get(i), itemValues.get(i));
                        }
                        mappedList.add(map);
                    }
                    result.put(prefix, mappedList);
                }
            }
        }

        return result;
    }

    /**
     * 解析一个值(简单字符串或列表)
     */
    private Object parseValue() {
        skipSpaces();
        if (pos < len && input.startsWith(":[", pos)) {
            return parseList();
        } else {
            // 普通值,直到 :| 或结尾
            int end = input.indexOf(":|", pos);
            if (end < 0) {
                end = len;
            }
            String value = input.substring(pos, end).trim();
            pos = end;
            return value;
        }
    }

    /**
     * 解析列表,返回 List<List<String>>
     * 每个子列表代表一个项的所有值(已去除首尾空格,内部空格保留)
     */
    private List<List<String>> parseList() {
        // 跳过 ":["
        pos += 2;
        // 跳过空格,然后查找 "]"
        skipSpaces();
        int posEnd = input.indexOf(":]", pos);
        if (posEnd < 0) {
            throw new IllegalArgumentException("Missing ':]' at position " + pos);
        }
        String listData = input.substring(pos, posEnd);
        List<List<String>> lists = parseListData(listData);
        pos = posEnd + 2; // 跳过 :]
        return lists;
    }

    /**
     * 解析列表数据(不含外层 :[ 和 :])
     * 严格校验:必须包含一个或多个项,每个项以 ":{ " 开始,以 ":}" 结束。
     */
    private List<List<String>> parseListData(String s) {
        List<List<String>> result = new ArrayList<>();
        if (s == null || s.isEmpty()) {
            // 空列表是合法的,返回空结果
            return result;
        }
        int i = 0;
        int len = s.length();

        while (i < len) {
            // 跳过项前的空格
            while (i < len && Character.isWhitespace(s.charAt(i))) i++;
            if (i >= len) break;

            // 必须出现项开始标记 :{
            if (!s.startsWith(":{", i)) {
                throw new IllegalArgumentException(
                    String.format("Expected ':{' at position %d (relative to list content), found '%s'",
                                  i, i < len ? s.substring(i, Math.min(i + 10, len)) : "<EOF>")
                );
            }
            i += 2; // 跳过 :{
            // 跳过项内字段前的空格
            while (i < len && Character.isWhitespace(s.charAt(i))) i++;

            // 查找项结束标记 :}
            int endMark = s.indexOf(":}", i);
            if (endMark < 0) {
                throw new IllegalArgumentException("Missing ':}' at position " + i + " in list content");
            }

            // 解析该行内的字段
            List<String> fields = new ArrayList<>();
            int fieldStart = i;
            while (fieldStart < endMark) {
                // 跳过字段前的空格
                while (fieldStart < endMark && Character.isWhitespace(s.charAt(fieldStart))) {
                    fieldStart++;
                }
                if (fieldStart >= endMark) break;

                // 查找字段分隔符 :,
                int commaIdx = s.indexOf(":,", fieldStart);
                if (commaIdx < 0 || commaIdx >= endMark) {
                    // 最后一个字段
                    String field = s.substring(fieldStart, endMark).trim();
                    fields.add(field);
                    break;
                } else {
                    String field = s.substring(fieldStart, commaIdx).trim();
                    // 允许空字段(即字段内容为空),但分隔符必须存在
                    fields.add(field);
                    fieldStart = commaIdx + 2; // 跳过 :,
                }
            }
            result.add(fields);
            i = endMark + 2; // 跳过 :}
        }

        // 如果还有剩余字符(非空格),则视为非法
        while (i < len && Character.isWhitespace(s.charAt(i))) i++;
        if (i < len) {
            throw new IllegalArgumentException(
                String.format("Unexpected characters after last item at position %d: '%s'",
                              i, s.substring(i, Math.min(i + 10, len)))
            );
        }

        return result;
    }

    // ========== 测试 ==========
    public static void main(String[] args) {

        String input = " bank:= 012:| details := :[  :{2:,0.063:,GA:} :{2:,0.063:,GA:}:]:|details-key:=:[:{stockValue:,stockItemValue:,moneyType:}:]:|bank2:= 012:|";

        Map<String, Object> result = new CustomParser(input).parse();
        for (Map.Entry<String, Object> entry : result.entrySet()) {
            System.out.println(entry.getKey() + "->" + entry.getValue());
        }

    }
}

状态机

java 复制代码
public class StateMachineParser {

    private final String input;
    private int pos;
    private final int len;
    private char ch;

    public StateMachineParser(String input) {
        this.input = input;
        this.len = input.length();
        this.pos = 0;
        this.ch = len > 0 ? input.charAt(0) : 0;
    }

    private void nextChar() {
        pos++;
        ch = pos < len ? input.charAt(pos) : 0;
    }

    private void skipWhitespace() {
        while (pos < len && Character.isWhitespace(ch)) {
            nextChar();
        }
    }

    private boolean matchNextTwo(char c1, char c2) {
        return pos + 1 < len && ch == c1 && input.charAt(pos + 1) == c2;
    }

    private void error(String msg) {
        throw new RuntimeException("Syntax error at position " + pos + ": " + msg);
    }

    public Map<String, Object> parse() {
        Map<String, Object> result = new LinkedHashMap<>();

        final int START = 0, KEY = 1, AFTER_KEY = 2,
                SIMPLE_VALUE = 3, LIST_START = 4, LIST_ITEM = 5;

        int state = START;
        StringBuilder keyBuf = new StringBuilder();
        StringBuilder valueBuf = new StringBuilder();

        List<List<String>> currentList = null;
        List<String> currentItem = null;
        String currentKey = null;

        while (pos < len) {
            switch (state) {
                case START:
                    skipWhitespace();
                    if (pos >= len) break;
                    keyBuf.setLength(0);
                    state = KEY;
                    break;

                case KEY:
                    if (matchNextTwo(':', '=')) {
                        currentKey = keyBuf.toString().trim();
                        if (currentKey.isEmpty()) {
                            error("Key cannot be empty before ':='");
                        }
                        nextChar(); nextChar();
                        state = AFTER_KEY;
                    } else {
                        if (ch == ':' && pos + 1 < len && input.charAt(pos + 1) != '=') {
                            error("Unexpected ':' in key (expected ':=')");
                        }
                        keyBuf.append(ch);
                        nextChar();
                    }
                    break;

                case AFTER_KEY:
                    skipWhitespace();
                    if (pos >= len) {
                        error("Unexpected end after key '" + currentKey + "'");
                    }
                    if (matchNextTwo(':', '[')) {
                        nextChar(); nextChar();
                        currentList = new ArrayList<>();
                        state = LIST_START;
                    } else {
                        valueBuf.setLength(0);
                        state = SIMPLE_VALUE;
                    }
                    break;

                case SIMPLE_VALUE:
                    if (matchNextTwo(':', '|')) {
                        String val = valueBuf.toString().trim();
                        result.put(currentKey, val);
                        nextChar(); nextChar();
                        state = START;
                    } else if (matchNextTwo(':', '[') || matchNextTwo(':', '{') ||
                               matchNextTwo(':', ']') || matchNextTwo(':', '}') ||
                               matchNextTwo(':', ',')) {
                        error("Unexpected sequence ':" + input.charAt(pos + 1) +
                              "' inside simple value");
                    } else {
                        valueBuf.append(ch);
                        nextChar();
                        if (pos >= len) {
                            result.put(currentKey, valueBuf.toString().trim());
                            state = START;
                        }
                    }
                    break;

                case LIST_START:
                    skipWhitespace();
                    if (pos >= len) {
                        error("List for key '" + currentKey + "' started but never closed with ':]'");
                    }
                    if (matchNextTwo(':', ']')) {
                        nextChar(); nextChar();
                        result.put(currentKey, currentList);
                        skipWhitespace();
                        if (pos < len) {
                            if (matchNextTwo(':', '|')) {
                                nextChar(); nextChar();
                                state = START;
                            } else {
                                error("Expected ':|' after list closing ':]' because more data follows");
                            }
                        } else {
                            state = START;
                        }
                    } else if (matchNextTwo(':', '{')) {
                        nextChar(); nextChar();
                        currentItem = new ArrayList<>();
                        state = LIST_ITEM;
                    } else {
                        error("Expected ':{' or ':]' in list for key '" + currentKey +
                              "', found '" + (ch != 0 ? ch : "EOF") + "'");
                    }
                    break;

                case LIST_ITEM:
                    valueBuf.setLength(0);
                    while (pos < len) {
                        if (matchNextTwo(':', ',')) {
                            currentItem.add(valueBuf.toString().trim());
                            nextChar(); nextChar();
                            break;
                        } else if (matchNextTwo(':', '}')) {
                            currentItem.add(valueBuf.toString().trim());
                            nextChar(); nextChar();
                            currentList.add(currentItem);
                            state = LIST_START;
                            break;
                        } else if (matchNextTwo(':', '[') || matchNextTwo(':', ']') ||
                                   matchNextTwo(':', '{') || matchNextTwo(':', '|')) {
                            error("Unexpected sequence ':" + input.charAt(pos + 1) +
                                  "' inside list item (expected ':,' or ':}')");
                        } else {
                            valueBuf.append(ch);
                            nextChar();
                        }
                    }
                    if (pos >= len && state == LIST_ITEM) {
                        error("List item not terminated with ':}' for key '" + currentKey + "'");
                    }
                    break;

                default:
                    throw new IllegalStateException("Invalid state: " + state);
            }
        }

        // ---------- 后处理:严格校验并合并 "-key" 与列表 ----------
        List<String> suffixKeys = new ArrayList<>();
        for (String k : result.keySet()) {
            if (k.endsWith("-key")) suffixKeys.add(k);
        }

        for (String keyWithSuffix : suffixKeys) {
            String prefix = keyWithSuffix.substring(0, keyWithSuffix.length() - 4);
            Object prefixVal = result.get(prefix);
            Object suffixVal = result.get(keyWithSuffix);

            // 必须两者都是 List
            if (!(prefixVal instanceof List) || !(suffixVal instanceof List)) {
                continue; // 按理不会发生,但可跳过
            }

            List<?> rawList = (List<?>) prefixVal;
            List<?> keyList = (List<?>) suffixVal;

            // 情况1:数据非空,但字段定义列表为空 -> 报错
            if (!rawList.isEmpty() && keyList.isEmpty()) {
                throw new RuntimeException("Key definition for '" + prefix + "' is empty but data exists");
            }

            // 情况2:数据为空,则无需合并,直接移除 -key 条目(可选)
            if (rawList.isEmpty()) {
                result.remove(keyWithSuffix);
                continue;
            }

            // 此时 rawList 非空,keyList 也非空
            // 检查 rawList 内部元素是否为 List
            if (!(rawList.get(0) instanceof List)) {
                throw new RuntimeException("Data list for '" + prefix + "' is not a list of lists");
            }
            // 检查 keyList 内部元素是否为 List
            if (!(keyList.get(0) instanceof List)) {
                throw new RuntimeException("Key definition list for '" + prefix + "' is not a list of lists");
            }

            @SuppressWarnings("unchecked")
            List<String> fieldNames = (List<String>) keyList.get(0);
            // 字段名列表不能为空
            if (fieldNames.isEmpty()) {
                throw new RuntimeException("Field name list for '" + prefix + "' is empty");
            }

            @SuppressWarnings("unchecked")
            List<List<String>> rows = (List<List<String>>) rawList;

            // 校验每一行的字段数
            for (int i = 0; i < rows.size(); i++) {
                List<String> row = rows.get(i);
                if (row.size() != fieldNames.size()) {
                    throw new RuntimeException("Data row " + i + " has " + row.size() +
                            " fields, but key definition has " + fieldNames.size() +
                            " fields for key '" + prefix + "'");
                }
            }

            // 转换
            List<Map<String, Object>> mappedList = new ArrayList<>(rows.size());
            for (List<String> row : rows) {
                Map<String, Object> map = new LinkedHashMap<>();
                for (int i = 0; i < fieldNames.size(); i++) {
                    map.put(fieldNames.get(i), row.get(i));
                }
                mappedList.add(map);
            }
            result.put(prefix, mappedList);
            result.remove(keyWithSuffix); // 移除原始的 -key 条目
        }

        return result;
    }

    // ========== 测试 ==========
    public static void main(String[] args) {
        // 原测试(合法)
        String input1 = " bank:= 012:|details:=:[:{2:,0.063:,GA:}:{2:,0.063:,GA:}:]:|details-key:=:[:{stockValue:,stockItemValue:,moneyType:}:]";
        for (Map.Entry<String, Object> entry : new StateMachineParser(input1).parse().entrySet()) {
            System.out.println(entry.getKey() + "->" + entry.getValue());
        }
    }
}
相关推荐
xcl092512 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐14 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长14 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn14 小时前
Spring 注解使用详解
java·spring
一条小小yu14 小时前
Spring IoC的理解
java·后端·spring
落魄实习生15 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈15 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白16 小时前
核心功能(Service接口)
java·mybatis
乌暮17 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习