Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

根据运算符优先级解析SQL规则表达式

Rest探路者 2019-02-27 10:44:00 阅读数:302 评论数:0 点赞数:0 收藏数:0

1.需求

测试数据库使用Greenplum,生产库使用GBase

普通表:存储客户数据,千万级别,结构如下

statdate代表日期;userid代表用户id;serialnumber代表手机号;AI72和A_I59是标签字段。+---------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+-------+ | statdate | varchar(255) | YES | | NULL | | | userid | varchar(255) | YES | | NULL | | | serialnumber | varchar(255) | YES | | NULL | | | AI72 | varchar(255) | YES | | NULL | | | A_I59 | varchar(255) | YES | | NULL | | +---------------+--------------+------+-----+---------+-------+

字典表:记录表的字段的位置。

+-------------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+---------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | columnname | text | YES | | NULL | | | tablename | text | YES | | NULL | | +-------------+---------+------+-----+---------+-------+

根据优先级解析如下表达式,返回结果集(String类型手机号的List),解析式中出现的字段不一定在同一个表中,'&'表示求交集,'|'表示求并集。

(AI72=1|AI59=0)&serial_number=1369567xxxx

再将结果集放入Redis,给同事使用,千万级的数据预估5GB以上,要注意内存的清理(服务器内存配置64GB)

2.知识准备

我尝试过将形如'(AI72=1|AI59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码,将其改造成业务代码

2.1 运算符优先级解析

先弄懂运算符优先级解析的原理,再继续逻辑///数字栈:用于存储表达式中的各个数字// private Stack numberStack = null;///符号栈:用于存储运算符和括号// private Stack symbolStack = null;

该博主用栈存储数字和符号,栈的特性众所周知,后进先出,为什么使用栈存储符号,稍后讲到

接下来看判断运算符优先级的方法//// 比较符号优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false /// private boolean comparePri(charsymbol) {if (symbolStack.empty()) { //空栈返回true return true; }//符号优先级说明(从高到低)://第1级: (//第2级: / ///第3级: + -//第4级: ) char top = (char) symbolStack.peek(); //查看堆栈顶部的对象,注意不是出栈 if (top == '(') {return true; }//比较优先级 switch(symbol) {case '(': //优先级最高 return true;case '/*': {if (top == '+' || top == '-') //优先级比+和-高 return true;else return false; }case '/': {if (top == '+' || top == '-') //优先级比+和-高 return true;else return false; }case '+':return false;case '-':return false;case ')': //优先级最低 return false;case '=': //结束符 return false;default:break; }return true; }

comparePri方法比较运算符优先级,优先级降序排序如下:'(','/* /','+ -',')',优先级在前的会先return true,结束switch,优先级低的元素会在栈底,优先级高的元素在栈顶参加运算,直到栈底上面的元素运算完毕,才轮到栈底元素

接下来,看判断表达式是否标准的方法private booleanisStandard(String numStr) {if (numStr == null || numStr.isEmpty()) //表达式不能为空 return false;//这里不使用栈也可以 Stack stack = new Stack(); //用来保存括号,检查左右括号是否匹配 boolean b = false; //用来标记'='符号是否存在多个 for (int i = 0; i < numStr.length(); i++) {char n =numStr.charAt(i);//判断字符是否合法//n+""将char转化成String if (!(isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| "+".equals(n + "") || "-".equals(n + "")|| "/*".equals(n + "") || "/".equals(n + "")|| "=".equals(n + ""))) {return false; }//将左括号压栈,用来给后面的右括号进行匹配 if ("(".equals(n + "")) { stack.push(n); }if (")".equals(n + "")) { //匹配括号//将左括号出栈,说明有右括号匹配到左括号 if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) //括号是否匹配 return false; }//检查是否有多个'='号//允许末尾出现一个'='号 if ("=".equals(n + "")) {if(b)return false; b= true; } }//可能会有缺少右括号的情况 if (!stack.isEmpty())return false;//检查'='号是否不在末尾 if (!("=".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true; }

isStandard方法匹配左括号,检测表达式末尾是否含有0个或1个'='

现在看核心的计算方法//// 解析并计算四则运算表达式(含括号),返回计算结果 / /@paramnumStr / 算术表达式(含括号)// public longcaculate(String numStr) { numStr= removeStrSpace(numStr); //去除空格//如果算术表达式尾部没有‘=’号,则在尾部添加‘=’,表示结束符 if (numStr.length() > 1 && !"=".equals(numStr.charAt(numStr.length() - 1) + "")) { numStr+= "="; }//检查表达式是否合法 if (!isStandard(numStr)) { System.err.println("错误:算术表达式有误!");return 0; }//初始化栈 numberStack = new Stack(); symbolStack= new Stack();//用于缓存数字,因为数字可能是多位的 StringBuffer temp = newStringBuffer();//从表达式的第一个字符开始处理 for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); //获取一个字符 if (isNumber(ch)) { //若当前字符是数字 temp.append(ch); //加入到数字缓存中 } else { //非数字的情况 String tempStr = temp.toString(); //将数字缓存转为字符串 if (!tempStr.isEmpty()) {long num = Long.parseLong(tempStr); //将数字字符串转为长整型数 numberStack.push(num); //将数字压栈 temp = new StringBuffer(); //重置数字缓存 }//判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把计算前面计算出来 while (!comparePri(ch) && !symbolStack.empty()) {long b = numberStack.pop(); //出栈,取出数字,后进先出 long a =numberStack.pop();//取出运算符进行相应运算,并把结果压栈进行下一次运算 switch ((char) symbolStack.pop()) {case '+': numberStack.push(a+b);break;case '-': numberStack.push(a-b);break;case '/': numberStack.push(a/*b);break;case '/': numberStack.push(a/b);break;default:break; } }//while循环结束 if (ch != '=') {//优先级高的符号入栈 symbolStack.push(newCharacter(ch));if (ch == ')') {//去掉左右括号 symbolStack.pop(); symbolStack.pop(); } } } }//for循环结束 return numberStack.pop(); //返回计算结果 }

运算好的结果会放在numStack栈底,后进入的数字在栈顶等待运算。我推荐园友打断点调试此段代码,方便理解

完整代码戳这里

2.2 流程分析

我以‘3/5+2/10’为例分析运算符解析的流程

'/*'运算符先入栈

 

3入栈,5再入栈

此时numStack中5出栈,3出栈,symbolStack中'/*'出栈,进入swtich

完成乘法运算,将结果入栈

此时栈的情况如下

现在symbolStack入栈'+',numStack入栈2,此时读取到了'/',根据comparePri方法,'/'在'+'前面,乘号具有更高的优先级,return true,此时入栈'/*'。这里我们看到了栈在此处的妙用,低优先级的运算符在栈底等待运算,栈顶存放高优先级运算符;

然后numStack入栈10,栈的情况如下

numStack中10和2出栈,symbolStack中'/'出栈,再进入switch,运算2/10,将结果20入栈numStack

此时栈的情况如下

最后一步,numStack中20,15相继出栈,symbolStack中'+'出栈,将结果35入栈numStack,此时symbolStack为空,我们返回numStack.pop(),numStack出栈,此时numStack也为空

 

3.解决方案

我尝试过将形如'(AI72=1|AI59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码,将其改造成业务代码

3.1 思路1

优先级排序如下:'(' > '&' > '|' > ')'

优先级的解析我们已经做好,现在解决业务部分

symbolStack存储符号,numStack存储结果集。这里我们不能像刚才数字运算那么做,因为条件(id<=5)和结果集不能合并。我们创建一个LinkedHashMap>存储结果集,并引入UUID,根据UUID前缀来判断是否是结果集;前缀有"UUID"则是结果集,否则是普通条件,调用JDBC工具去数据库查询结果集。总共有四种情况:a和b都含有'UUID',a和b都不含有"UUID",a含有"UUID,b不含",b含有"UUID,a不含".

运算完毕,清除map,求交集使用retainAll方法,并集使用addAll方法packagecom.advance.analysis;importcom.advance.JDBC.ConnectGBase;importcom.advance.Redis.RedisUtil;importorg.apache.log4j.Logger;importorg.testng.annotations.Test;importjava.sql.ResultSet;importjava.sql.SQLException;import java.util./;//// @Author: 谷天乐 / @Date: 2019/2/18 19:32 / @Description: 修正版/*/ public classAnalysis {public String UUIDPREFIX = "UUID";public String UUIDPREFIXAND = "UUIDAnd";public String UUIDPREFIXOR = "UUIDOr";private static Logger logger = Logger.getLogger(Analysis.class);//// 条件栈:用于存储表达式中的各个条件// private Stack numberStack = null;//// 符号栈:用于存储运算符和括号// private Stack symbolStack = null;//// 解析并计算规则表达式(含括号),返回计算结果 / /@paramnumStr 规则表达式(含括号)// public LinkedHashMap> caculate(String numStr) throwsSQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { numStr= removeStrSpace(numStr); //去除空格//如果规则表达式尾部没有‘/#’号,则在尾部添加‘/#’,表示结束符 if (numStr.length() > 1 && !"/#".equals(numStr.charAt(numStr.length() - 1) + "")) { numStr+= "/#"; }//检查表达式是否合法 if (!isStandard(numStr)) { System.err.println("错误:规则表达式有误!");return null; }//初始化栈 numberStack = new Stack(); symbolStack= new Stack();//用于缓存条件,因为条件可能是多位的 StringBuffer temp = newStringBuffer();//用于缓存执行sql的临时结果集//有序Map LinkedHashMap> tempMap = new LinkedHashMap<>();//从表达式的第一个字符开始处理 for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); //获取一个字符 if (!isSign(ch)) { //若当前字符不是符号 temp.append(ch); //加入到条件缓存中 } else { //非数字的情况 String tempStr = temp.toString(); //将条件缓存转为字符串 if (!tempStr.isEmpty()) { numberStack.push(tempStr);//将条件压栈 temp = new StringBuffer(); //重置条件缓存 }//判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来 while (!comparePri(ch) && !symbolStack.empty()) { String b= numberStack.pop(); //出栈,取出条件,后进先出 String a =numberStack.pop();//取出运算符进行相应运算,并把结果压栈进行下一次运算 switch ((char) symbolStack.pop()) {case '&':if(a.contains(UUIDPREFIX)&&b.contains(UUIDPREFIX)){ bothHaveUUID(tempMap,"and"); }else if(!a.contains(UUIDPREFIX)&&b.contains(UUIDPREFIX)){ optionalUUID(tempMap,a,UUIDPREFIXAND,"and"); }else if(a.contains(UUIDPREFIX)&&!b.contains(UUIDPREFIX)){ optionalUUID(tempMap,b,UUIDPREFIXAND,"and"); }else{ bothDontHaveUUID(a,b,tempMap,UUIDPREFIXAND,"and"); }break;case '|'://根据列名查表名 if(a.contains(UUIDPREFIX)&&b.contains(UUIDPREFIX)){ bothHaveUUID(tempMap,"or"); }else if(!a.contains(UUIDPREFIX)&&b.contains(UUIDPREFIX)){ optionalUUID(tempMap,a,UUIDPREFIXOR,"or"); }else if(a.contains(UUIDPREFIX)&&!b.contains(UUIDPREFIX)){ optionalUUID(tempMap,b,UUIDPREFIXOR,"or"); }else{ bothDontHaveUUID(a,b,tempMap,UUIDPREFIXOR,"or"); }break;default:break; } }//while循环结束 if (ch != '/#') { symbolStack.push(new Character(ch)); //符号入栈 if (ch == ')') { //去括号 symbolStack.pop(); symbolStack.pop(); } } } }//for循环结束//使用entrySet会@throws ConcurrentModificationException//清缓存:去掉Map中无用的Set,计算过程中的Set在a含有'UUID',b不含有'UUID',//b含有'UUID',a不含有'UUID'的情况被清除//清除最终运算结果,即a和b都含有'UUID',a和b都不含有'UUID'的情况 Set>> entries =tempMap.entrySet(); Iterator>> iteratorMap =entries.iterator();while(iteratorMap.hasNext()){ Map.Entry> next =iteratorMap.next();if(getTail(tempMap).getKey()!=next.getKey()) iteratorMap.remove(); }return tempMap; //返回计算结果 }//// 去除字符串中的所有空格// privateString removeStrSpace(String str) {return str != null ? str.replaceAll(" ", "") : ""; }//// 检查算术表达式的基本合法性,符合返回true,否则false// private booleanisStandard(String numStr) {if (numStr == null || numStr.isEmpty()) //表达式不能为空 return false; Stack stack = new Stack(); //用来保存括号,检查左右括号是否匹配 boolean b = false; //用来标记'/#'符号是否存在多个 for (int i = 0; i < numStr.length(); i++) {char n =numStr.charAt(i);//判断字符是否合法 if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| ">".equals(n + "") || "<".equals(n + "")|| "&".equals(n + "") || "|".equals(n + "")|| "=".equals(n + "") || "/#".equals(n + "")|| "".equals(n + "") || "!".equals(n + "")|| "-".equals(n + "") || ".".equals(n + "")|| "'".equals(n + ""))) {return false; }//将左括号压栈,用来给后面的右括号进行匹配 if ("(".equals(n + "")) { stack.push(n); }if (")".equals(n + "")) { //匹配括号 if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) //括号是否匹配 return false; }//检查是否有多个'/#'号 if ("/#".equals(n + "")) {if(b)return false; b= true; } }//可能会有缺少右括号的情况 if (!stack.isEmpty())return false;//检查'/#'号是否不在末尾 if (!("/#".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true; }//// 判断一个字符是否是 ( ) $ |等符号// private boolean isSign(charc) {if (c == '(' || c == ')' || c == '&' || c == '|' || c == '/#') {return true; }else{return false; } }//提取列名 privateString extractColomnName(String str){//去掉比较运算符 if(str.indexOf("<")!=-1) str= str.substring(0,str.indexOf("<"));if(str.indexOf("=")!=-1) str= str.substring(0,str.indexOf("="));if(str.indexOf(">")!=-1) str= str.substring(0,str.indexOf(">"));returnstr; }//// 判断一个字符是否是 a-z A-Z 之间的字母// private boolean isChar(charc) {if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {return true; }else{return false; } }//// 判断字符是否是0-9的数字// private boolean isNumber(charnum) {if (num >= '0' && num <= '9')return true;return false; }//// 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false// private boolean comparePri(charsymbol) {if (symbolStack.empty()) { //空栈返回ture return true; }//符号优先级说明(从高到低)://第1级: (//第2级: $//第3级: |//第4级: ) char top = (char) symbolStack.peek(); //查看堆栈顶部的对象,注意不是出栈 if (top == '(') {return true; }//比较优先级 switch(symbol) {case '(': //优先级最高 return true;case '&': {if (top == '|') //优先级比|高 return true;else return false; }case '|': {return false; }case ')': //优先级最低 return false;case '/#': //结束符 return false;default:break; }return true; }//List转成String public String listToString(List list, charseparator) { StringBuilder sb= newStringBuilder();for (int i = 0; i < list.size(); i++) { sb.append(list.get(i)).append(separator); }return sb.toString().substring(0, sb.toString().length() - 1); }//JDK1.8 public static Map.Entry getTailByReflection(LinkedHashMap map) throws NoSuchFieldException, IllegalAccessException { Field tail = map.getClass().getDeclaredField("tail"); tail.setAccessible(true); return (Map.Entry) tail.get(map); }// public Map.Entry getTail(LinkedHashMapmap) { Iterator> iterator =map.entrySet().iterator(); Map.Entry tail = null;while(iterator.hasNext()) { tail=iterator.next(); }returntail; }//// @Author 谷天乐 / @Description a和b都含有'UUID' / @Date 2019/2/25 11:25 / @Param [tempMap, andor] /*@returnvoid /*/*/ public void bothHaveUUID(LinkedHashMap>tempMap,String andor){int num = 0; List keys= newArrayList();for (Map.Entry>entry : tempMap.entrySet()) { keys.add(entry.getKey()); num++; } Set resultAndSet1= tempMap.get(keys.get(num-2)); Set resultAndSet2= tempMap.get(keys.get(num-1));//求交集 if(andor.equals("and")) resultAndSet1.retainAll(resultAndSet2);if(andor.equals("or")) resultAndSet1.addAll(resultAndSet2); String andkey=String.valueOf(UUID.randomUUID()); andkey= "UUIDAnd" + andkey.toString().replace("-", ""); numberStack.push(andkey); tempMap.put(andkey, resultAndSet1); }//// @Author 谷天乐 / @Description a和b都不含有"UUID" / @Date 2019/2/25 12:37 / @Param [conditionA, conditionB, tempMap, UUID_prefix, andor] /*@returnvoid /*/*/ public void bothDontHaveUUID(String conditionA,String conditionB,LinkedHashMap> tempMap,String UUID_prefix,String andor) throwsSQLException, ClassNotFoundException { String aOrColumnName=extractColomnName(conditionA); String bOrColumnName=extractColomnName(conditionB); StringBuffer getOrTableSqlA= new StringBuffer("select table_name from table_dictionary "); getOrTableSqlA.append("where column_name="); getOrTableSqlA.append("'"); getOrTableSqlA.append(aOrColumnName); getOrTableSqlA.append("'"); ResultSet tableOrNameRSA=Connect_GBase.query(getOrTableSqlA.toString()); List tableOrListA= Connect_GBase.resultSetToList(tableOrNameRSA, "table_name"); String getOrTableSqlB= "select table_name from table_dictionary " + "where column_name=" + "'" + bOrColumnName + "'"; ResultSet tableOrNameRSB=Connect_GBase.query(getOrTableSqlB); List tableOrListB= Connect_GBase.resultSetToList(tableOrNameRSB, "table_name"); StringBuffer selectOrSql1= new StringBuffer("select /* from ");if(tableOrListA.size()==0)throw new RuntimeException("字段不存在"); selectOrSql1.append(listToString(tableOrListA,' ')); selectOrSql1.append(" where "); selectOrSql1.append(conditionA); ResultSet rsOr1=Connect_GBase.query(selectOrSql1.toString()); StringBuffer selectOrSql2= new StringBuffer("select /* from ");if(tableOrListB.size()==0)throw new RuntimeException("字段不存在"); selectOrSql2.append(listToString(tableOrListB,' ')); selectOrSql2.append(" where "); selectOrSql2.append(conditionB); ResultSet rsOr2=Connect_GBase.query(selectOrSql2.toString()); List resultOrList1= Connect_GBase.resultSetToList(rsOr1, null); List resultOrList2= Connect_GBase.resultSetToList(rsOr2, null); Set resultOrSet1= newHashSet(resultOrList1); Set resultOrSet2= newHashSet(resultOrList2);//求并集 if(andor.equals("or")) resultOrSet1.addAll(resultOrSet2);if(andor.equals("and")) resultOrSet1.retainAll(resultOrSet2);//对每个条件进行执行,把执行出来的结果进行求并集操作,把结果放到栈中 String orkey =String.valueOf(UUID.randomUUID()); orkey= UUID_prefix + orkey.toString().replace("-", ""); numberStack.push(orkey); tempMap.put(orkey, resultOrSet1); }//*/* @Author 谷天乐 /* @Description Or 运算如下情况: /* a含有'UU_ID',b不含有'UUID';b含有'UU_ID',a不含有'UUID' /* @Date 2019/2/20 9:34 /* @Param [tempMap, condition] /* @return void /*/*/ public void optionalUUID(LinkedHashMap> tempMap,String condition,String UUID_prefix,String andor) throwsSQLException, ClassNotFoundException {int num = 0; List keys= newArrayList();for (Map.Entry>entry : tempMap.entrySet()) { keys.add(entry.getKey()); num++; } Set resultOrSet1= tempMap.get(keys.get(num-1));//根据列名查表名 String aOrColumnName =extractColomnName(condition); StringBuffer getOrTableSqlA= new StringBuffer("select table_name from table_dictionary "); getOrTableSqlA.append("where column_name="); getOrTableSqlA.append("'"); getOrTableSqlA.append(aOrColumnName); getOrTableSqlA.append("'"); ResultSet tableOrNameRSA=Connect_GBase.query(getOrTableSqlA.toString()); List tableOrListA= Connect_GBase.resultSetToList(tableOrNameRSA, "table_name");if(tableOrListA.size()==0)throw new RuntimeException("字段不存在"); StringBuffer selectOrSql2= new StringBuffer("select /* from "); selectOrSql2.append(listToString(tableOrListA,' ')); selectOrSql2.append(" where "); selectOrSql2.append(condition); ResultSet rsOr2=Connect_GBase.query(selectOrSql2.toString()); List resultOrList2= Connect_GBase.resultSetToList(rsOr2, null); Set resultOrSet2= newHashSet(resultOrList2);//求并集 if (andor.equals("or")) resultOrSet1.addAll(resultOrSet2);if (andor.equals("and")) resultOrSet1.retainAll(resultOrSet2);//移除使用过的Set tempMap.remove(keys.get(num-1)); String orkey=String.valueOf(UUID.randomUUID()); orkey= UUID_prefix + orkey.toString().replace("-", "");//对每个条件进行执行,把执行出来的结果进行求交集操作,把结果放到栈中 numberStack.push(orkey);//--1、把a和b解析成sql并执行获取结果集,使用UUID生成key放入tempMap中, tempMap.put(orkey, resultOrSet1); }//根据Map获取指定key的value public Set getSpecifiedListFromMap(LinkedHashMap> resultMap, String key,String id) throwsException{ Set set = new HashSet<>();for (Map.Entry>entry : resultMap.entrySet()) {newRedisUtil().del(id);for(Map maplist : entry.getValue()){ set.add(maplist.get(key));newRedisUtil().lpush(id,String.valueOf(maplist.get(key))); } } logger.debug(Analysis.class+"Jedis插入数据成功");returnset; } @Testpublic void test() throwsException {//a>=20 & (b<=40|c!=1) | d//id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)//id<=5|id<=5&amt=45.00|id<=6//(date='2013-07-22'|date='2013-02-22')&id<=7&phone_number=18895358020 String num = "(A_I72=1&A_I59=0)"; //默认的算式 Analysis analysis = newAnalysis(); LinkedHashMap> resultMap =analysis.caculate(num); getSpecifiedListFromMap(resultMap,"serial_number","ssss1111"); logger.debug(resultMap); } }

3.2 思路2

先算结果集,再判断运算条件是否含有“UUID_”前缀,运算过程中清理map

这个是同事写的版本,代码少139行packagecom.advance.analysis;importcom.advance.EngineandMessage.util.NonUtil;importcom.advance.JDBC.ConnectGreenplum;importorg.apache.log4j.LogManager;importorg.apache.log4j.Logger;importjava.sql.ResultSet;import java.util./;//// @Title: Analysiszk.java / @Description: 规则解析工具 /@author: zhangkuo / @date: 2019-2-18 上午9:03:28// public classAnalysiszk {private static Logger logger = LogManager.getLogger(Analysis.class);//// 条件栈:用于存储表达式中的各个条件// private Stack numberStack = null;//// 符号栈:用于存储运算符和括号// private Stack symbolStack = null;//// / @Title: caculate / @Description: 解析并计算规则表达式(含括号),返回计算结果 / @Author: zhangkuo /@param:@paramnumStr 规则表达式(含括号) /@param:@return/@param:@throwsException /@return: LinkedHashMap> /@throws // public Set caculate(String numStr) throwsException { numStr= removeStrSpace(numStr); //去除空格//如果规则表达式尾部没有‘/#’号,则在尾部添加‘/#’,表示结束符 if (numStr.length() > 1 && !"/#".equals(numStr.charAt(numStr.length() - 1) + "")) { numStr+= "/#"; }//检查表达式是否合法 if (!isStandard(numStr)) { logger.debug("错误:规则表达式有误!");return null; }//初始化栈 numberStack = new Stack(); symbolStack= new Stack();//用于缓存条件,因为条件可能是多位的 StringBuffer temp = newStringBuffer();//用于缓存执行sql的临时结果集//有序Map HashMap> tempMap = new HashMap<>();//从表达式的第一个字符开始处理 for (int i = 0; i < numStr.length(); i++) {char ch = numStr.charAt(i); //获取一个字符 if (!isSign(ch)) { //若当前字符不是符号 temp.append(ch); //加入到条件缓存中 } else { //非数字的情况 String tempStr = temp.toString(); //将条件缓存转为字符串 if (!tempStr.isEmpty()) { numberStack.push(tempStr);//将条件压栈 temp = new StringBuffer(); //重置条件缓存 }//判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来 while (!comparePri(ch) && !symbolStack.empty()) { String b= numberStack.pop(); //出栈,取出条件,后进先出 String a =numberStack.pop();//取出运算符进行相应运算,并把结果压栈进行下一次运算 switch ((char) symbolStack.pop()) {case '&': Set set1 = this.calculator(b,tempMap); Set set2 = this.calculator(a,tempMap); String key= "UUID"+UUID.randomUUID(); set1.retainAll(set2); tempMap.put(key, set1); numberStack.push(key);if(a.contains("UUID")){ tempMap.remove(a); }if(b.contains("UUID")){ tempMap.remove(b); }break;case '|': Set set3 = this.calculator(b,tempMap); Set set4 = this.calculator(a,tempMap); String keyOr= "UUID"+UUID.randomUUID(); set3.addAll(set4); tempMap.put(keyOr, set3); numberStack.push(keyOr);if(a.contains("UUID")){ tempMap.remove(a); }if(b.contains("UUID")){ tempMap.remove(b); }break;default:break; } }//while循环结束 if (ch != '/#') { symbolStack.push(new Character(ch)); //符号入栈 if (ch == ')') { //去括号 symbolStack.pop(); symbolStack.pop(); } } } }//for循环结束 return tempMap.get(numberStack.pop().toString()); //返回计算结果 }//根据传入的字符串进行运算并返回结果集 public Set calculator(String resultset,Map> tempMap) throwsException{ SettempSet;if(resultset.contains("UUID")){ tempSet=tempMap.get(resultset); }else{//根据列名查表名 String columnName =extractColomnName(resultset); StringBuffer getTableName= new StringBuffer("select tablename from tabledictionary "); getTableName.append("where columnname="); getTableName.append("'"); getTableName.append(columnName); getTableName.append("'"); ResultSet tableName=ConnectGreenplum.query(getTableName.toString()); List tableList= ConnectGreenplum.resultSetToList(tableName, "tablename");if(NonUtil.isNon(tableList)){throw new RuntimeException("字段不存在"); } StringBuffer queryResult= new StringBuffer("select / from "); queryResult.append(listToString(tableList,' ')); queryResult.append(" where "); queryResult.append(resultset); ResultSet result=ConnectGreenplum.query(queryResult.toString()); List resultList= ConnectGreenplum.resultSetToList(result, null); tempSet= newHashSet(resultList); }returntempSet; }//// 去除字符串中的所有空格// privateString removeStrSpace(String str) {return str != null ? str.replaceAll(" ", "") : ""; }//// 检查算术表达式的基本合法性,符合返回true,否则false// private booleanisStandard(String numStr) {if (numStr == null || numStr.isEmpty()) //表达式不能为空 return false; Stack stack = new Stack(); //用来保存括号,检查左右括号是否匹配 boolean b = false; //用来标记'/#'符号是否存在多个 for (int i = 0; i < numStr.length(); i++) {char n =numStr.charAt(i);//判断字符是否合法 if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")|| ">".equals(n + "") || "<".equals(n + "")|| "&".equals(n + "") || "|".equals(n + "")|| "=".equals(n + "") || "/#".equals(n + "")|| "_".equals(n + "") || "!".equals(n + "")|| "-".equals(n + "") || ".".equals(n + "")|| "'".equals(n + ""))) {return false; }//将左括号压栈,用来给后面的右括号进行匹配 if ("(".equals(n + "")) { stack.push(n); }if (")".equals(n + "")) { //匹配括号 if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) //括号是否匹配 return false; }//检查是否有多个'/#'号 if ("/#".equals(n + "")) {if(b)return false; b= true; } }//可能会有缺少右括号的情况 if (!stack.isEmpty())return false;//检查'/#'号是否不在末尾 if (!("/#".equals(numStr.charAt(numStr.length() - 1) + "")))return false;return true; }//// 判断一个字符是否是 ( ) $ |等符号// private boolean isSign(charc) {if (c == '(' || c == ')' || c == '&' || c == '|' || c == '/#') {return true; }else{return false; } }//提取列名 privateString extractColomnName(String str){//去掉比较运算符 if(str.indexOf("<")!=-1) str= str.substring(0,str.indexOf("<"));if(str.indexOf("=")!=-1) str= str.substring(0,str.indexOf("="));if(str.indexOf(">")!=-1) str= str.substring(0,str.indexOf(">"));returnstr; }//// 判断一个字符是否是 a-z A-Z 之间的字母// private boolean isChar(charc) {if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {return true; }else{return false; } }//// 判断字符是否是0-9的数字// private boolean isNumber(charnum) {if (num >= '0' && num <= '9')return true;return false; }//// 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false/*/ private boolean comparePri(charsymbol) {if (symbolStack.empty()) { //空栈返回ture return true; }//符号优先级说明(从高到低)://第1级: (//第2级: $//第3级: |//第4级: ) char top = symbolStack.peek(); //查看堆栈顶部的对象,注意不是出栈 if (top == '(') {return true; }//比较优先级 switch(symbol) {case '(': //优先级最高 return true;case '&': {if (top == '|') //优先级比|高 return true;else return false; }case '|': {return false; }case ')': //优先级最低 return false;case '/#': //结束符 return false;default:break; }return true; }//List转成String public String listToString(List list, charseparator) { StringBuilder sb= newStringBuilder();for (int i = 0; i < list.size(); i++) { sb.append(list.get(i)).append(separator); }return sb.toString().substring(0, sb.toString().length() - 1); }//测试 public static void main(String args[]) throwsException {//a>=20 & (b<=40|c!=1) | d//id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)//id<=5|id<=5&amt=45.00|id<=6 String num = "(date='2013-02-22'&date='2013-03-01')|id<=3&amt>=500.00"; //默认的算式 Analysis_zk analysis = newAnalysis_zk(); Set resultMap =analysis.caculate(num); logger.debug(resultMap); } }

 所需要的依赖包在这里

 

版权声明
本文为[Rest探路者]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/Java-Starter/p/10438807.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领