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<Long> numberStack = null; /** 符號棧:用於存儲運算符和括號 */ private Stack<Character> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配 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<Long>(); symbolStack = new Stack<Character>(); // 用於緩存數字,因為數字可能是多位的 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工具去數據庫查詢結果集。總共有四種情況:a和b都含有'UUID_',a和b都不含有"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<String> numberStack = null; /** * 符號棧:用於存儲運算符和括號 */ private Stack<Character> symbolStack = null; /** * 解析並計算規則表達式(含括號),返回計算結果 * * @param numStr 規則表達式(含括號) */ public LinkedHashMap<String, Set<Map>> 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<String>(); symbolStack = new Stack<Character>(); // 用於緩存條件,因為條件可能是多位的 StringBuffer temp = new StringBuffer(); //用於緩存執行sql的臨時結果集 //有序Map LinkedHashMap<String, Set<Map>> 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<Map.Entry<String, Set<Map>>> entries = tempMap.entrySet(); Iterator<Map.Entry<String, Set<Map>>> iteratorMap = entries.iterator(); while (iteratorMap.hasNext()){ Map.Entry<String, Set<Map>> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配 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 <K, V> Map.Entry<K, V> getTailByReflection(LinkedHashMap<K, V> map) throws NoSuchFieldException, IllegalAccessException { Field tail = map.getClass().getDeclaredField("tail"); tail.setAccessible(true); return (Map.Entry<K, V>) tail.get(map); }*/ public <K, V> Map.Entry<K, V> getTail(LinkedHashMap<K, V> map) { Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator(); Map.Entry<K, V> 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<String, Set<Map>> tempMap,String andor){ int num = 0; List keys = new ArrayList(); for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> 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<String, Set<Map>> tempMap,String condition,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException { int num = 0; List keys = new ArrayList(); for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> resultMap, String key,String id) throws Exception{ Set<Object> set = new HashSet<>(); for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> 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<String> numberStack = null; /** * 符號棧:用於存儲運算符和括號 */ private Stack<Character> symbolStack = null; /** * * @Title: caculate * @Description: 解析並計算規則表達式(含括號),返回計算結果 * @Author: zhangkuo * @param: @param numStr 規則表達式(含括號) * @param: @return * @param: @throws Exception * @return: LinkedHashMap<String,Set<String>> * @throws */ public Set<String> 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<String>(); symbolStack = new Stack<Character>(); // 用於緩存條件,因為條件可能是多位的 StringBuffer temp = new StringBuffer(); //用於緩存執行sql的臨時結果集 //有序Map HashMap<String, Set<String>> 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<String> set1 = this.calculator(b,tempMap); Set<String> 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<String> set3 = this.calculator(b,tempMap); Set<String> 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<String> calculator(String resultset,Map<String,Set<String>> tempMap) throws Exception{ Set<String> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配 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<String> resultMap = analysis.caculate(num); logger.debug(resultMap); } }
所需要的依賴包在這里