博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
根据运算符优先级解析SQL规则表达式
阅读量:5157 次
发布时间:2019-06-13

本文共 27469 字,大约阅读时间需要 91 分钟。

1.需求

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

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

stat_date代表日期;user_id代表用户id;serial_number代表手机号;A_I72和A_I59是标签字段。

+---------------+--------------+------+-----+---------+-------+| Field         | Type         | Null | Key | Default | Extra |+---------------+--------------+------+-----+---------+-------+| stat_date     | varchar(255) | YES  |     | NULL    |       || user_id       | varchar(255) | YES  |     | NULL    |       || serial_number | varchar(255) | YES  |     | NULL    |       || A_I72         | varchar(255) | YES  |     | NULL    |       || A_I59         | varchar(255) | YES  |     | NULL    |       |+---------------+--------------+------+-----+---------+-------+

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

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

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

(A_I72=1|A_I59=0)&serial_number=1369567xxxx

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

2.知识准备

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

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

2.1 运算符优先级解析

先弄懂运算符优先级解析的原理,再继续逻辑

/** 数字栈:用于存储表达式中的各个数字 */    private Stack
numberStack = null; /** 符号栈:用于存储运算符和括号 */ private Stack
symbolStack = null;

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

接下来看判断运算符优先级的方法

/**     * 比较符号优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false     * ✔     */    private boolean comparePri(char symbol) {        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 boolean isStandard(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个'='

现在看核心的计算方法

/**     * 解析并计算四则运算表达式(含括号),返回计算结果     * ✔     * @param numStr     *            算术表达式(含括号)     */    public long caculate(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 = new StringBuffer(); // 从表达式的第一个字符开始处理 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(new Character(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.解决方案

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

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

3.1 思路1

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

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

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

运算完毕,清除map,求交集使用retainAll方法,并集使用addAll方法

package com.advance.analysis;import com.advance.JDBC.Connect_GBase;import com.advance.Redis.RedisUtil;import org.apache.log4j.Logger;import org.testng.annotations.Test;import java.sql.ResultSet;import java.sql.SQLException;import java.util.*;/** * @Author: 谷天乐 * @Date: 2019/2/18 19:32 * @Description: 修正版 */public class Analysis {    public String UUID_PREFIX = "UUID_";    public String UUID_PREFIX_AND = "UUID_And_";    public String UUID_PREFIX_OR = "UUID_Or_";    private static Logger logger = Logger.getLogger(Analysis.class);    /**     * 条件栈:用于存储表达式中的各个条件     */    private Stack
numberStack = null; /** * 符号栈:用于存储运算符和括号 */ private Stack
symbolStack = null; /** * 解析并计算规则表达式(含括号),返回计算结果 * * @param numStr 规则表达式(含括号) */ public LinkedHashMap
> caculate(String numStr) throws SQLException, 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 = new StringBuffer(); //用于缓存执行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(UUID_PREFIX)&&b.contains(UUID_PREFIX)){ bothHaveUUID(tempMap,"and"); }else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){ optionalUUID(tempMap,a,UUID_PREFIX_AND,"and"); }else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){ optionalUUID(tempMap,b,UUID_PREFIX_AND,"and"); } else { bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_AND,"and"); } break; case '|': //根据列名查表名 if(a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){ bothHaveUUID(tempMap,"or"); }else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){ optionalUUID(tempMap,a,UUID_PREFIX_OR,"or"); }else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){ optionalUUID(tempMap,b,UUID_PREFIX_OR,"or"); }else { bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_OR,"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; // 返回计算结果 } /** * 去除字符串中的所有空格 */ private String removeStrSpace(String str) { return str != null ? str.replaceAll(" ", "") : ""; } /** * 检查算术表达式的基本合法性,符合返回true,否则false */ private boolean isStandard(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(char c) { if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') { return true; } else { return false; } } //提取列名 private String 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(">")); return str; } /** * 判断一个字符是否是 a-z A-Z 之间的字母 */ private boolean isChar(char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { return true; } else { return false; } } /** * 判断字符是否是0-9的数字 */ private boolean isNumber(char num) { if (num >= '0' && num <= '9') return true; return false; } /** * 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false */ private boolean comparePri(char symbol) { 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, char separator) { StringBuilder sb = new StringBuilder(); 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(LinkedHashMap
map) { Iterator
> iterator = map.entrySet().iterator(); Map.Entry
tail = null; while (iterator.hasNext()) { tail = iterator.next(); } return tail; } /** * @Author 谷天乐 * @Description a和b都含有'UUID_' * @Date 2019/2/25 11:25 * @Param [tempMap, andor] * @return void **/ public void bothHaveUUID(LinkedHashMap
> tempMap,String andor){ int num = 0; List keys = new ArrayList(); 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 = "UUID_And_" + 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] * @return void **/ public void bothDontHaveUUID(String conditionA,String conditionB,LinkedHashMap
> tempMap,String UUID_prefix,String andor) throws SQLException, 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 = new HashSet(resultOrList1); Set resultOrSet2 = new HashSet(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) throws SQLException, ClassNotFoundException { int num = 0; List keys = new ArrayList(); 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 = new HashSet(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) throws Exception{ Set
set = new HashSet<>(); for (Map.Entry
> entry : resultMap.entrySet()) { new RedisUtil().del(id); for(Map maplist : entry.getValue()){ set.add(maplist.get(key)); new RedisUtil().lpush(id,String.valueOf(maplist.get(key))); } } logger.debug(Analysis.class+"Jedis插入数据成功"); return set; } @Test public void test() throws Exception { //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 = new Analysis(); LinkedHashMap
> resultMap = analysis.caculate(num); getSpecifiedListFromMap(resultMap,"serial_number","ssss1111"); logger.debug(resultMap); }}

3.2 思路2

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

这个是同事写的版本,代码少139行

package com.advance.analysis;import com.advance.Engine_and_Message.util.NonUtil;import com.advance.JDBC.Connect_Greenplum;import org.apache.log4j.LogManager;import org.apache.log4j.Logger;import java.sql.ResultSet;import java.util.*;/** * @Title:  Analysis_zk.java * @Description: 规则解析工具 * @author: zhangkuo * @date:   2019-2-18 上午9:03:28 */public class Analysis_zk {    private static Logger logger = LogManager.getLogger(Analysis.class);    /**     * 条件栈:用于存储表达式中的各个条件     */    private Stack
numberStack = null; /** * 符号栈:用于存储运算符和括号 */ private Stack
symbolStack = null; /** * * @Title: caculate * @Description: 解析并计算规则表达式(含括号),返回计算结果 * @Author: zhangkuo * @param: @param numStr 规则表达式(含括号) * @param: @return * @param: @throws Exception * @return: LinkedHashMap
> * @throws */ public Set
caculate(String numStr) throws Exception { 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 = new StringBuffer(); //用于缓存执行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) throws Exception{ Set
tempSet; if(resultset.contains("UUID_")){ tempSet = tempMap.get(resultset); }else{ //根据列名查表名 String columnName = extractColomnName(resultset); StringBuffer getTableName = new StringBuffer("select table_name from table_dictionary "); getTableName.append("where column_name="); getTableName.append("\'"); getTableName.append(columnName); getTableName.append("\'"); ResultSet tableName = Connect_Greenplum.query(getTableName.toString()); List tableList = Connect_Greenplum.resultSetToList(tableName, "table_name"); 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 = Connect_Greenplum.query(queryResult.toString()); List resultList = Connect_Greenplum.resultSetToList(result, null); tempSet = new HashSet(resultList); } return tempSet; } /** * 去除字符串中的所有空格 */ private String removeStrSpace(String str) { return str != null ? str.replaceAll(" ", "") : ""; } /** * 检查算术表达式的基本合法性,符合返回true,否则false */ private boolean isStandard(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(char c) { if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') { return true; } else { return false; } } //提取列名 private String 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(">")); return str; } /** * 判断一个字符是否是 a-z A-Z 之间的字母 */ private boolean isChar(char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { return true; } else { return false; } } /** * 判断字符是否是0-9的数字 */ private boolean isNumber(char num) { if (num >= '0' && num <= '9') return true; return false; } /** * 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false */ private boolean comparePri(char symbol) { 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, char separator) { StringBuilder sb = new StringBuilder(); 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[]) throws Exception { //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 = new Analysis_zk(); Set
resultMap = analysis.caculate(num); logger.debug(resultMap); }}

 所需要的依赖包在

 

转载于:https://www.cnblogs.com/Java-Starter/p/10438807.html

你可能感兴趣的文章
学习什么语言的问题,其实,不是一个问题......
查看>>
MongoRepository动态代理及jpa方法解析源码分析
查看>>
bzoj2015 [Usaco2010 Feb]Chocolate Giving
查看>>
bzoj1651[Usaco2006 Feb]Stall Reservations 专用牛棚
查看>>
spring中InitializingBean接口使用理解
查看>>
团队合作之Scrum
查看>>
关于开发和测试沟通的一些问题
查看>>
Redis教程_2
查看>>
通过java给qq邮箱发送信息
查看>>
style、currentStyle、getComputedStyle区别介绍
查看>>
Python List(列表)使用示例
查看>>
poj-3069-Saruman's Army
查看>>
webstorm的破解
查看>>
C#中创建线程,创建带参数的线程
查看>>
让 VS2010 支持 HTML5 和 CSS3.0
查看>>
eclipse 中过滤空包,目录树中不显示。
查看>>
test
查看>>
从 PHP 到 Java
查看>>
OpenOffice 实现OFFICE在线预览
查看>>
Stardew Valley(星露谷物语)Mod开发之路 写在前面
查看>>