一种高容错率的消息缓冲队列

Posted by tianhaoo on January 28, 2021 本文阅读量

问题描述

需要用java解析收到的键值对消息,格式为key:value,key的种类已知,但是发来的消息会像下面那样断断续续,难以直接解析消息。

ID: 60000000001
IP: esdt.elevat
orstar.com
PORT
:30080
TICK:60
APN:
CPIN:0
C
SQ:0
NET:00
LIFT:00000000000
000000
CALL:000
0000000000000000
0000000000000000
0000000000000

解决方案

本文提出一种高容错率的缓冲消息队列,以冒号为标的物,实现消息的高效解析,代码如下。

package com.tianhaoo.lift_and_dici_app;



import androidx.core.util.Pair;

import java.util.Deque;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class MsgQueue {
    private String[] keyWords = {"LIFT", "CALL", "ID", "IP", "PORT", "TICK", "APN", "CPIN", "CSQ", "NET"};
    private String[] keyWordsTwo = {"ID", "IP"};
    private String[] keyWordsThree = {"APN", "CSQ", "NET"};
    private String[] keyWordsFour = {"CALL", "CPIN", "LIFT", "PORT", "TICK"};
    private Deque<String> deque;

    MsgQueue(){
        // 初始化队列
        deque = new LinkedList<String>();
    }

    // 判断数组里是否包含字符串
    private boolean arrayHasString(String[] arr, String s){
        for(String t : arr){
            if(t.equals(s)){
                return true;
            }
        }
        return false;
    }

    // 判断队列里是否有超过两个冒号
    private boolean hasMoreThanTwoColon(){
        int colonCount = 0;
        for(String s : deque){
            if(s.equals(":")){
                colonCount ++;
                if(colonCount >= 2){
                    return true;
                }
            }
        }
        return false;
    }

    // 根据两个冒号位置,检查两个冒号前面是否都是合法的key
    // 都合法的话返回<key1, key2>,否则返回null
    private Pair<String, String> checkMsgKey(String candidateMsg, int colonIndex){

        String key1, key2;
        // 检查第一个冒号前的key
        if(colonIndex >=2 && arrayHasString(keyWordsTwo, candidateMsg.substring(colonIndex-2, colonIndex))){
            key1 = candidateMsg.substring(colonIndex-2, colonIndex);
        }else if(colonIndex >=3 && arrayHasString(keyWordsThree, candidateMsg.substring(colonIndex-3, colonIndex))){
            key1 = candidateMsg.substring(colonIndex-3, colonIndex);
        }else if(colonIndex >=4 && arrayHasString(keyWordsFour, candidateMsg.substring(colonIndex-4, colonIndex))){
            key1 = candidateMsg.substring(colonIndex-4, colonIndex);
        }else{
            key1 = null;
        }

        // 检查第二个冒号前的key
        int length = candidateMsg.length();
        if(length >=2 && arrayHasString(keyWordsTwo, candidateMsg.substring(length-2))){
            key2 = candidateMsg.substring(length-2);
        }else if(length >=3 && arrayHasString(keyWordsThree, candidateMsg.substring(length-3))){
            key2 = candidateMsg.substring(length-3);
        }else if(length >=4 && arrayHasString(keyWordsFour, candidateMsg.substring(length-4))){
            key2 = candidateMsg.substring(length-4);
        }else{
            key2 = null;
        }

        if(key1 != null && key2 != null){
            // 两个key 都合法
            return new Pair<>(key1, key2);
        }else{
            // 至少有一个key是不合法的
            return null;
        }
    }

    // 找到并返回队列里的第一条key合法的消息
    private Pair<String, String> takeCandidateMsg(){
        // 已经确定队列里有>=2个冒号再执行这个方法
        // 粗略的按照冒号和已知的关键字,拿到一条可能的消息
        // 如果两侧的消息都不对就返回null
        int colonCount = 0;
        int colonIndex = 0;
        int index = 0;
        StringBuilder sb = new StringBuilder();
        for(String s : deque){
            // 记录冒号数量
            if(s.equals(":")){
                colonCount ++;
                // 记录第一个冒号位置
                if(colonCount == 1){
                    colonIndex = index;
                }
            }


            // 把第二个冒号前面的都放进sb
            if(colonCount <= 1){
                sb.append(s);
            }else{
                break;
            }

            index += 1;
        }
        // candidateMsg从头开始一直到第二个冒号前
        // 检查消息key是否合法
        String candidateMsg = sb.toString();
        Pair<String, String> pair = checkMsgKey(candidateMsg, colonIndex);
        if(pair != null){
            String key1 = pair.first;
            String key2 = pair.second;
            // 现在candidateMsg从key1开始一直到第二个冒号前
            candidateMsg = candidateMsg.substring(colonIndex-key1.length());  // 确定key1之后就可以把前面多余的去掉
            int valueLength = candidateMsg.length()-key1.length()-key2.length();
            String value = candidateMsg.substring(key1.length()+1, key1.length()+valueLength);  

            return new Pair<String, String>(key1, value);
        }else{
            // 两侧的key不合法
            return null;
        }

    }

    // 检查消息value是否合法
    private boolean checkMsgValue(Pair<String, String> pair){
        // 这个函数可以用于判断value格式是否正确,比如位数是否正确,值的范围是否正确等等。
        String key = pair.first;
        String value = pair.second;
        switch (key){
            case "LIFT":
                if(value.length() != 17){
                    System.out.println("LIFT消息不是17位");
                    return false;
                }
                break;
            case "CALL":
                if(value.length() != 48){
                    System.out.println("CALL消息不是48位");
                    return false;
                }
                break;
            case "ID":

                break;
            case "IP":

                break;
            case "PORT":

                break;
            case "TICK":

                break;
            case "APN":

                break;
            case "CPIN":

                break;
            case "CSQ":
                break;
            case "NET":

                break;
            default:
                // 不认识的key,不合法
                return false;

        }
        return true;

    }

    // 从队列头开始删除指定长度的元素
    private void deleteMsgFromFirst(){
        // 将第一个冒号之前的内容全删除,包括第一个冒号
        // 确保现在有两个冒号再来调用此方法
        // 不管消息合法与否,最后都要调用一下这个方法
        while(!deque.pollFirst().equals(":")){ }
    }




    // 把新收到的字符串消息一个字符一个字符地放进队列
    public void putMsg(String msg){
        Pattern pattern = Pattern.compile("[0-9]|[A-Z]|[a-z]|:|\\.");
        for(int i=0; i<msg.length(); i++){
            String t = String.valueOf(msg.charAt(i));
            Matcher isNum = pattern.matcher(t);

            if(!isNum.matches()){
                // 正则表达式没匹配到的字符直接忽略
            }else{
                deque.offerLast(t);
            }
        }
    }

    public Pair<String, String> retrieveMsg(){
        // if没有超过两个冒号 就返回 null
        // else寻找有合法key的消息
        //      if没有合法key的消息,就删除到第二个冒号后面, 返回null
        //      else检查value的合法性
        //          if消息value合法,返回,然后把这个消息remove掉
        //          else不返回,直接把这个消息remove

        if(!hasMoreThanTwoColon()){
            return null;
        }else{
            Pair<String, String> pair = takeCandidateMsg();

            deleteMsgFromFirst();  // 删掉队列首的消息
            if(pair == null){
                return null;
            }else{
                if(!checkMsgValue(pair)){
                    return null;
                }else{
                    return pair;
                }
            }

        }
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        for(String s : deque){
            sb.append(s);
        }
        return sb.toString();
    }
}