解析自定义数据

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());
        }
    }
}
相关推荐
wuminyu1 小时前
JDK21中FFM api的upcall回调机制解析
java·linux·c语言·jvm·c++
桦说编程1 小时前
深入理解 FutureTask 状态机——从契约到实现
java·后端·性能优化
松仔log1 小时前
Kotlin中级——协程
java·jvm·kotlin
wifi___6 小时前
全局异常处理的原理
java·开发语言
2601_9638702010 小时前
【计算机毕业设计】基于Spring Boot的专科医院医疗管理系统
java·spring boot·课程设计
Bingo_BIG10 小时前
Java Spring 批量修改,实体、接口、方法的定义
java·spring
画中有画11 小时前
软件架构中质量属性(性能、安全、可扩展性)的权衡设计
java·运维·安全
Shaoxi Zhang11 小时前
JAVA学习笔记035——对象和JSON格式
java·笔记·学习