由于近期开发工作涉及到动态指标的计算输出,把度娘都问烦了还是没有找到合适的解决方案(强迫症,为了这一个功能而引入第三方依赖不值得 [捂脸] )。
先上运算符对应的ASCLL值方案一、尝试逐字节解析,遇到运算符或结尾,将已缓存的两个参数及运算符号计算结果输出
import java.util.*;
import java.util.regex.Matcher;
public float deal(String formula, Class> indicator, Object data) throws NoSuchFieldException, IllegalAccessException {
formula = formula.replaceAll(" ", "");
char[] chars = formula.toCharArray();
String patten = "\(\)*+-/\[]";
Matcher matcher = Pattern.compile(patten).matcher(formula);
float result = 0;
StringBuilder temp = new StringBuilder();
char lastSign = 20;
float lastParam = 0F;
float curParam = 0F;
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c != 20) {
if (signs.contains(c)) {
curParam = appendFinish(temp, indicator);
result = innerDeal(lastParam, curParam, lastSign);
lastSign = c;
} else {
temp.append(c);
}
}
}
return result;
}
private float appendFinish(StringBuilder temp, Class> indicator) throws NoSuchFieldException, IllegalAccessException {
if (temp.toString().equals("100%")) return 1.0f;
return indicator.getDeclaredField(temp.toString()).getFloat(null);
}
public float innerDeal(float lastParam, float curValue, char operator)
throws NoSuchFieldException, IllegalAccessException {
switch (operator) {
case 20: {
}
case 91:
case 40:
case 42:
return lastParam * curValue;
case 43:
return lastParam + curValue;
case 45:
return lastParam - curValue;
case 47:
return lastParam / curValue;
case 41:
case 93:
return lastParam;
}
return 0;
}
结果发现这个方案只能处理运算符优先级相同的公式,如果存在括号或者加减与乘除取模同时出现的时候需要递归或者嵌套业务处理,这肯定不行啊,优先级低的再前面,那得缓存多少临时结果才合适?而且如果公式里面多放几个括号那得加多少业务代码?很明显这不符合强迫症患者的风格,方案一告逝
几经曲折,有那么一瞬间脑洞大开,甚至天灵盖都被掀开的那种,然后出现了方案二
方案二
1、还是对公式的字节遍历,非运算符的字符直接拼接临时串(StringBuilder),遇到运算符则先将临时串(完整的属性名)利用反射获取源数据中的属性值,并将属性值拼接到最终串(StringBuilder),并重置临时串,最终得到纯数值的计算公式。
公式计算,根据运算符的优先级,首先用括号切分,然后对各部分的乘除取模运算优先计算,接着是加减,最后就是括号切分的各部分再次计算。
好家伙,还是回到方案一的坑里了?难道最终还是要用第三方依赖吗?那指定是不服啊,咋整?莽吧,终于“JavaScript”这个词给我蹦了出来,想到了JS是可以处理这种东西的,可是却忘记rt包里本身就是有可以执行JS脚本的引擎,结了,给我整!!
方案三、使用JDK的rt包的JS引擎对纯数值公式计算
package com.dr.service;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class FormulaUtils {
static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
static Set signs = new HashSet() {{
this.addAll(Arrays.asList(new Character[]{20, 37, 40, 41, 42, 43, 45, 47, 91, 93}));
}};
static Class> temp;
public static double calculateFormula(String formula, Object param) throws Exception {
if (!param.getClass().equals(temp)) temp = param.getClass();
formula = formula.replaceAll(" ", "") + (char) 20;
char[] chars = formula.toCharArray();
StringBuilder resStr = new StringBuilder();
if (chars.length > 0) {
StringBuilder tempStr = new StringBuilder();
for (char cur : chars) {
if (signs.contains(cur)) {
if (tempStr.length() > 0) {
String column = tempStr.toString();
if (column.equals("100%")) resStr.append("1");
else if (isNumeric(column)) resStr.append(column);
else {
Field field = temp.getDeclaredField(column);
field.setAccessible(true);
Object value = field.get(param);
resStr.append(value);
tempStr = new StringBuilder();
}
}
if (resStr.length() > 0 && cur != 20) resStr.append(cur);
} else tempStr.append(cur);
}
}
return (double) jse.eval(resStr.toString());
}
public static boolean isNumeric(String str) {
return Pattern.compile("[-+]?(\d+|\d+\.+\d+)$").matcher(str).matches();
}
}
测试方法
public static void main(String[] args) throws Exception {
ArrayList strings = new ArrayList() {{
add("a+b-c/d+(f+g)+e");
add("a-b+c/d+(f+g)+e");
add("a*b-c/d+(f+g)+e");
add("a/b+c/d+(f+g)+e");
add("a%b+c/d+(f+g)+e");
add("a*b+c/d+(f+g)+e");
}};
for (String formula : strings) {
int a = 0;
long millis = System.currentTimeMillis();
System.out.println(millis);
do {
FormulaUtils.calculateFormula(formula, new CalculateParam());
a++;
} while (a < 200000);
System.out.println(System.currentTimeMillis() - millis + "====" + a);
}
System.out.println(1.1 + 2.2 - 3.3 / 4.4 + (6.6 + 7.7) + 5.5);
结果如下
1659669622577 3540====200000 1659669626117 2148====200000 1659669628266 2174====200000 1659669630441 2269====200000 1659669632710 2384====200000 1659669635094 2192====200000 22.35至此,完事,经测试发现第一次会因为获取反射对象耗时偏高,后续二十万次计算耗时都稳定在2200-2500ms上下,考虑通用,把反射对象缓存,后续考虑使用反射池优化。
感谢浏览,觉得有用的话还望点赞收藏



