问题描述
需要用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();
}
}