[注]:使用的話多學習example;
一.基本簡介
Aviator是一個高性能、輕量級的 java 語言實現的表達式求值引擎.
1.1運行方式
其他輕量級的求值器一般都是通過解釋的方式運行, 而Aviator則是直接將表達式編譯成 JVM 字節碼, 交給 JVM 去執行。
1.2 aviator特性
-
支持絕大多數運算操作符,包括算術操作符、關系運算符、邏輯操作符、位運算符、正則匹配操作符(=~)、三元表達式(?😃;
-
支持操作符優先級和括號強制設定優先級;
-
邏輯運算符支持短路運算;
-
支持豐富類型,例如nil、整數和浮點數、字符串、正則表達式、日期、變量等,支持自動類型轉換;
-
內置一套強大的常用函數庫;
-
可自定義函數,易於擴展;
-
可重載操作符;
-
支持大數運算(BigInteger)和高精度運算(BigDecimal);
-
性能優秀。
二.使用
Aviator的入口類是com.googlecode.aviator.AviatorEvaluator
,包括編譯和執行表達式的方法(通過調用AviatorEvaluatorInstance
單例實例對應的方法):
- 獲取
AviatorEvaluatorInstance
對象:AviatorEvaluator
使用的是單例實例:
/**
* 單例 靜態的求值程序實例
*/
private static class StaticHolder {
private static AviatorEvaluatorInstance INSTANCE = new AviatorEvaluatorInstance();
}
/**
* 獲取單例 的求值程序實例
*/
public static AviatorEvaluatorInstance getInstance() {
return StaticHolder.INSTANCE;
}
/** 如果想再不同的場景使用不同的 求值器,則可通過一下方法獲取
* Create a aviator evaluator instance.
* <p></p> 創建一個 表達式求值程序實例
*/
public static AviatorEvaluatorInstance newInstance() {
return new AviatorEvaluatorInstance();
}
- 編譯和執行方法:
/**
* Compile a text expression to Expression object
* <p></p>使用指定的緩存模式編譯字符串形式的表達式。<p></p>
*
* 編譯后的表達式存放在 AviatorEvaluatorInstance 的 ConcurrentHashMap<String, FutureTask<Expression>> cacheExpressions
* = new ConcurrentHashMap<String, FutureTask<Expression>>();
* 中,通過表達式cacheExpressions.get(expKey).get()獲取。
*
* @param expression text expression
* @param cached Whether to cache the compiled result,make true to cache it.
* @return
*/
public static Expression compile(final String expression, final boolean cached) {
/**
* 1)獲取 求值程序實例
* 2)編譯表達式——在 求值程序類中計算
*/
return getInstance().compile(expression, cached);
}
/**
* Execute a text expression with environment<p></p>
* 執行一個表達式,使用指定的環境和緩存策略——如果不指定則默認不綁定參數環境和緩存
*
* @param expression text expression
* @param env Binding variable environment 綁定變量的"環境"
* @param cached Whether to cache the compiled result,make true to cache it.
*/
public static Object execute(String expression, Map<String, Object> env, boolean cached) {
return getInstance().execute(expression, env, cached);
}
2.1 執行表達式&&數字和選項Option類
1)執行簡單表達式只要一行:
Long result = (Long) AviatorEvaluator.execute("1+2+3");
- 對於數值類型,Aviator只支持Long和Double—Integer和Float會自動轉換;
- 可通過
AviatorEvaluator.setOption(Options.XXX, true);
對計算進行配置,比如...Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true)
會將表達式中的浮點數解析成BigDecimal。其他選項包括 選項列表說明:
/**
* 計算多行表達式時,是否跟蹤計算過程:打開后將在控制台打印整個表達式的求值過程。
*請勿在生產環境打開,將極大地降低性能。默認為 false 關閉
*/
TRACE_EVAL,
2)三元表達式(if-else)
Aviator 沒有提供if else語句,但是提供了三元運算符?:,形式為bool ? exp1: exp2。 其中bool必須為Boolean類型的表達式, 而exp1和exp2可以為任何合法的 Aviator 表達式,並且不要求exp1和exp2返回的結果類型一致。
public static void main(String[] args) {
Expression expression=AviatorEvaluator.compile("a>b? a+b:a*b");
Map<String,Object> env=new HashMap<>();
env.put("a",2);
env.put("b",1);
System.out.println(expression.execute(env));
}
3)重載操作符
類aviator.lexer.token.OperatorType
包含了Aviator支持的各種運算符,如果程序中有對運算符重載的需要,則可以通過Aviator.addOpFunction(XX,new AbstractFunction(){call})
進行操作符的重載,也可以使用這個方法添加操作符
System.out.println(AviatorEvaluator.execute("1+2"));
new Thread(()->
AviatorEvaluator.addOpFunction(OperatorType.ADD, new AbstractFunction() {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
return new AviatorString("override");
}
//Get the function name:獲取函數名稱
@Override
public String getName() {
return "+";
}
})
).start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->System.out.println(AviatorEvaluator.execute("1+2"))).start();
TimeUnit.SECONDS.sleep(1);
System.out.println(AviatorEvaluator.newInstance().execute("1+2"));
output:
3
override
3
- 操作符和“操作符說明”放在
AvaitorEvaluatorInstance
中,是其非靜態成員變量——因此除了使用AviatorEvaluator.newInstance()
方法,所有計算都公用一套操作符解釋。所以第三次輸出結果又是3。
4)正則表達式
字符串與正則表達式通過 =~
操作符來匹配,結果為一個 Boolean 類 型, 因此可以用於三元表達式判斷。正則表達式規則跟 Java 完全一樣,因為內部其實就是使用java.util.regex.Pattern做編譯的
Map<String,Object> env=new HashMap<>();
env.put("email","dugk@foxmail.com");
System.out.println(AviatorEvaluator.execute("email=~ /([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1:'unknow'", env));
env.put("email","xxx");
System.out.println(AviatorEvaluator.execute("email=~ /([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1:'unknow'", env));
output:
dugk
unknow
-
Aviator 會自動將匹配成功的捕獲分組(capturing groups) 放入 env ${num}的變量中,其中$0 指代整個匹配的字符串,而$1表示第一個分組,$2表示第二個分組以此類推;
-
分組捕獲放入 env 是默認開啟的,因此如果傳入的 env 不是線程安全並且被並發使用,可能存在線程安全的隱患,不同的環境對應不同的Env。關閉分組匹配,可以通過
AviatorEvaluator.setOption(Options.PUT_CAPTURING_GROUPS_INTO_ENV, false)
; 來關閉,對性能有稍許好處。下圖是程序代碼執行到將結果放進環境map對應的結果map(Env繼承了Map)代碼處的時候:
2.2 編譯表達式:提高性能
以上執行表達式實際上是執行了編譯+執行的動作。更好的方式是: 先編譯一個含有變量的表達式,然后執行含有不同變量的環境。這樣性能更高。 示例如下:
//compile方法可以將表達式編譯成Expression的中間對象,
//當要執行表達式的時候傳入env並調用Expression的execute方法即可
public static void main(String[] args){
String expStr="a*b";
Expression expression=AviatorEvaluator.compile(expStr);
Map<String,Object> env=new HashMap<>();//注意一定要是<String,Object>的
env.put("a",3L);
env.put("b",2L);
System.out.println("result:"+expression.execute(env));
}
- 啟用緩存:調用
AviatorEvaluator.compile(String,boolean)
第二個參數為true即可
public static Expression compile(String expression, boolean cached){}
- 緩存是一個map數據結構
/** FutureTask 的 call()方法執行具體的計算;get()方法可以阻塞獲取計算結果
* Compiled Expression cache。操作包含:
* 1.查詢get、清空clear、加入put、移除remove,后三種操作都需要加鎖
* 2.compile中會先查詢,后加入;
* 3.可顯式調用清空緩存:防止大對象;
*
* 4.使用緩存的話會產生競爭——緩存對象會被鎖住而無法被其他線程寫入。fixme 但是查詢get不需要加鎖
*/
private final ConcurrentHashMap<String, FutureTask<Expression>> cacheExpressions = new ConcurrentHashMap<String, FutureTask<Expression>>();
- 移除某個表達式緩存通過如下方法:
public static void invalidateCache(String expression);
2.3 優化級別
運行時優化傾向,有兩種選擇:
AviatorEvaluator.EVAL
,默認值,以運行時的性能優先,編譯會花費更多時間做優化,目前會做一些常量折疊、公共變量提取的優化。適合長期運行的表達式。 表達式經常不會變化。AviatorEvaluator.COMPILE
不會做任何編譯優化,犧牲一定的運行性能,適合需要頻繁編譯表達式的場景, 比如經常編譯不同的表達式。
設置和獲取優化級別的代碼如下:
AviatorEvaluator.setOptimize(1);
AviatorEvaluator.getInstance().getOption(Options.OPTIMIZE_LEVEL);