Aviator介紹


[注]:使用的話多學習example;

一.基本簡介

Aviator是一個高性能、輕量級的 java 語言實現的表達式求值引擎.

1.1運行方式

其他輕量級的求值器一般都是通過解釋的方式運行, 而Aviator則是直接將表達式編譯成 JVM 字節碼, 交給 JVM 去執行

1.2 aviator特性
  1. 支持絕大多數運算操作符,包括算術操作符、關系運算符、邏輯操作符、位運算符、正則匹配操作符(=~)、三元表達式(?😃;

  2. 支持操作符優先級和括號強制設定優先級;

  3. 邏輯運算符支持短路運算;

  4. 支持豐富類型,例如nil、整數和浮點數、字符串、正則表達式、日期、變量等,支持自動類型轉換;

  5. 內置一套強大的常用函數庫

  6. 自定義函數,易於擴展;

  7. 可重載操作符;

  8. 支持大數運算(BigInteger)和高精度運算(BigDecimal);

  9. 性能優秀。

二.使用

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)代碼處的時候:
    image

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);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM