1. 程式人生 > >Aviator-開源輕量級、高效能的表示式求值器

Aviator-開源輕量級、高效能的表示式求值器

一、輪子的必要性


    表示式的求值上,java的選擇非常多,強大的如Groovy、JRuby,N年沒維護的beanshell,包括javaeye上朋友的IKExpression。為什麼還需要Aviator?或者說Aviator的特點是什麼?

    我將Aviator定位在Groovy這樣全功能的指令碼和IKExpression這樣的簡易的表示式求值之間的東西,如果你不希望帶上Groovy那麼龐大的jar卻只用上一點點的功能,如果你希望功能和效能上比IKExpression好那麼一些,那麼也許你可以考慮Aviator。

    Aviator的設計思路跟利用GroovyObject的求值是一樣,通過編譯並動態生成位元組碼的方式將表示式編譯成一個類,然後反射執行這個類,因此會在效率上比純解釋執行的IKExpression好一些。

aviator結構圖

二、讓輪子轉起來。

求算術表示式

Java程式碼 
  1. import com.googlecode.aviator.AviatorEvaluator;  
  2. public class SimpleExample {  
  3.     public static void main(String[] args) {  
  4.         Long result = (Long) AviatorEvaluator.execute("1+2+3");  
  5.         System.out.println(result);  
  6.     }  
  7. }  

執行入口統一為AviatorEvaluator類,它有一系列靜態方法。

邏輯表示式和關係運算

Java程式碼 
  1. AviatorEvaluator.execute("3>1 && 2!=4 || true");  

Aviator支援所有的關係運算符和算術運算子,不支援位運算,同時支援表示式的優先順序,優先順序跟Java的運算子一樣,並且支援通過括號來強制優先順序。

使用變數和字串相加

Java程式碼 
  1. String yourname = “aviator”;  
  2.  Map<String, Object> env = new HashMap<String, Object>();  
  3.  env.put("yourname"
    , yourname);  
  4.  String result = (String) AviatorEvaluator.execute(" 'hello ' + yourname ", env);  
  5.  System.out.println(result);  

 列印:

Java程式碼 
  1. hello aviator  

字串可以單引號也可以雙引號括起來,並且支援轉義字元。變數名稱只要是合法的java identifer即可,變數需要使用者傳入,通過Map<String,Object>指定變數名和值是什麼,這裡的變數是yourname。

變數的訪問支援巢狀訪問,也就是dot操作符來訪問變數裡的屬性,假設我們有一個Foo類:

Java程式碼 
  1. public static class Foo {  
  2.        int i;  
  3.        float f;  
  4.        Date date = new Date();  
  5.        public Foo(int i, float f, Date date) {  
  6.            super();  
  7.            this.i = i;  
  8.            this.f = f;  
  9.            this.date = date;  
  10.        }  
  11.        public int getI() {  
  12.            return i;  
  13.        }  
  14.        public void setI(int i) {  
  15.            this.i = i;  
  16.        }  
  17.        public float getF() {  
  18.            return f;  
  19.        }  
  20.        public void setF(float f) {  
  21.            this.f = f;  
  22.        }  
  23.        public Date getDate() {  
  24.            return date;  
  25.        }  
  26.        public void setDate(Date date) {  
  27.            this.date = date;  
  28.        }  
  29.    }  

然後在使用一個表示式來描述Foo裡的各種屬性:

Java程式碼 
  1. Foo foo = new Foo(1003.14f, new Date());  
  2. Map<String, Object> env = new HashMap<String, Object>();  
  3. env.put("foo", foo);  
  4. String result =  
  5.         (String) AviatorEvaluator.execute(  
  6.             " '[foo i='+ foo.i + ' f='+foo.f+' year='+(foo.date.year+1900)+ ' month='+foo.date.month +']' ",  
  7.             env);  

 我們可以通過foo.date.year的方式來訪問變數foo中date屬性的year值,這是利用commons-beanutils的反射功能實現的,前提是你的變數是合法的JavaBean(public、getter缺一不可)。

三元表示式

Java程式碼 
  1. AviatorEvaluator.execute("3>0? 'yes':'no'");  

上面都還是一個求值器表示式的常見功能,下面要描述的是Aviator的一些偏指令碼性的功能。

類Ruby、Perl的正則匹配,匹配email地址:

Java程式碼 
  1. AviatorEvaluator.execute("'killme2008'=~/([\\w0-8][email protected]\\w+[\\.\\w+]+)/ ");  

 成功的話返回true,否則返回false。//括起來的字元序列成為正則表示式,=~操作符用於匹配。匹配只能在String和Pattern之間。

匹配成功,獲得匹配的分組,利用變數$digit

Java程式碼 
  1. AviatorEvaluator.execute("'[email protected]'=~/([\\w0-8][email protected]\\w+[\\.\\w+]+)/ ? $1:'unknow'");  

 匹配成功返回$1,表示第一個匹配的分組,也就是使用者名稱 killme2008

函式呼叫

Java程式碼 
  1. AviatorEvaluator.execute("sysdate()");  

 sysdate()是一個內建函式,返回當前日期,跟new java.util.Date()效果相同。

更多內建函式:

Java程式碼 
  1. AviatorEvaluator.execute("string.length('hello')");    // 求字串長度  
  2. AviatorEvaluator.execute("string.contains('hello','h')");  //判斷字串是否包含字串  
  3. AviatorEvaluator.execute("string.startsWith('hello','h')");  //是否以子串開頭  
  4. AviatorEvaluator.execute("string.endsWith('hello','llo')");  是否以子串結尾  
  5. AviatorEvaluator.execute("math.pow(-3,2)");   // 求n次方  
  6. AviatorEvaluator.execute("math.sqrt(14.0)");   //開平方根  
  7. AviatorEvaluator.execute("math.sin(20)");    //正弦函式  

可以看到Aviator的函式呼叫風格非常類似lua或者c。

自定義函式,實現AviatorFunction介面並註冊即可,比如我們實現一個add函式用於相加:

Java程式碼 
  1. import com.googlecode.aviator.AviatorEvaluator;  
  2. import com.googlecode.aviator.runtime.function.FunctionUtils;  
  3. import com.googlecode.aviator.runtime.type.AviatorDouble;  
  4. import com.googlecode.aviator.runtime.type.AviatorFunction;  
  5. import com.googlecode.aviator.runtime.type.AviatorObject;  
  6. class AddFunction implements AviatorFunction {  
  7.         public AviatorObject call(Map<String, Object> env, AviatorObject... args) {  
  8.             if (args.length != 2) {  
  9.                 throw new IllegalArgumentException("Add only supports two arguments");  
  10.             }  
  11.             Number left = FunctionUtils.getNumberValue(0, args, env);  
  12.             Number right = FunctionUtils.getNumberValue(1, args, env);  
  13.             return new AviatorDouble(left.doubleValue() + right.doubleValue());  
  14.         }  
  15.         public String getName() {  
  16.             return "add";  
  17.         }  
  18.     }  

註冊並呼叫:

Java程式碼 
  1. AviatorEvaluator.addFunction(new AddFunction());  
  2. System.out.println(AviatorEvaluator.execute("add(1,2)"));  
  3. System.out.println(AviatorEvaluator.execute("add(add(1,2),100)"));  

函式可以巢狀呼叫。

三、不公平的效能測試

  基本介紹完了,最後給些測試的資料,下列的測試場景都是每個表示式預先編譯,然後執行1000萬次,測量執行耗時。

場景1:

算術表示式

1000+100.0*99-(600-3*15)/(((68-9)-3)*2-100)+10000%7*71

結果:

測試 耗時(單位:秒)
Aviator 14.0
Groovy 79.6
IKExpression 159.2

場景2:

計算邏輯表示式和三元表示式混合:

6.7-100>39.6?5==5?4+5:6-1:!(100%3-39.0<27)?8*2-199:100%3

測試結果:

測試 耗時(單位:秒)
Aviator 11.0
Groovy 13.0
IKExpression 168.8

場景3:

計算算術表示式和邏輯表示式的混合,帶有5個變數的表示式:

i * pi +(d * b -199)/(1- d * pi)-(2+100- i / pi)%99==i * pi +(d * b -199)/(1- d * pi)-(2+100- i / pi)%99

變數設定為:

int i =100;float pi =3.14f;double d =-3.9;byte b =(byte)4;booleanbool=false;

每次執行前都重新設定這些變數的值。

結果:

測試 耗時(單位:秒)
Aviator 31.2
Groovy 9.7
IKExpression 編譯錯誤

場景4:

  • Aviator執行 sysdate()
  • groovy執行 new java.util.Date()
  • IKExpression執行 $SYSDATE()

結果:

測試 耗時(單位:秒)
Aviator 22.6
Groovy 13.9
IKExpression 25.4

原始的測試報告在這裡

四、結語

能看到這裡,並且感興趣的朋友請點選專案主頁:

下載地址:

完整的使用者手冊:

目前版本仍然是1.0.0-RC,希望更多朋友試用並最終release。有什麼疑問或者建議請跟貼。

相關推薦

Aviator-開源輕量級高效能表示式

一、輪子的必要性     表示式的求值上,java的選擇非常多,強大的如Groovy、JRuby,N年沒維護的beanshell,包括javaeye上朋友的IKExpression。為什麼還需要Aviator?或者說Aviator的特點是什麼?     我將Aviat

資料結構Java實現——①棧-->棧的應用三算術表示式

package org.Stone6762.MStack.adopt; import java.util.Scanner; import org.Stone6762.MStack.imple.LinkStack; /** * @author_Stone6762 */ public class Arit

資料結構之-鏈式棧及其常見應用(進位制轉換括號匹配行編輯程式表示式等)

1、棧的概念 棧(stack)又名堆疊,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧

leetcode 150. 逆波蘭表示式string轉int

150. 逆波蘭表示式求值 根據逆波蘭表示法,求表示式的值。 有效的運算子包括 +, -, *, / 。每個運算物件可以是整數,也可以是另一個逆波蘭表示式。 說明: 整數除法只保留整數部分。 給定逆波蘭表示式總是有效的。換句話說,表示式總會得出有效數值且不存在除

棧及其應用(表示式括號匹配)

一、棧(stack) 1、棧的特點   棧(Stack)是一種線性儲存結構,它具有如下特點: 【Note】: (1)棧中的資料元素遵守”先進後出”(First In Last Out)的原則,簡

棧---定義應用(遞迴字尾表示式實現數學表示式

一、定義 棧是限定僅在表尾進行插入和刪除操作的線性表。因此,棧的表尾端稱為棧頂;表頭端稱為棧底。不含任何資料元素的棧稱為空棧。棧又稱為後進先出(Last In First Out)的線性表,簡稱LIF0結構。 理解棧的定義需要注意:首先它是一個線性表,也即棧

使用棧實現表示式

看書學了一晚上這個內容,終於實現了 分為三個步驟:   0. 檢查輸入是否有誤(因為輸入其他的非預期字元,程式就會崩潰,我就試著加了一個檢查輸入的函式)   1. 先將正常的中綴表示式轉換為字尾表示式   2. 再進行求值 根據字尾表示式求值比較簡單,因為字尾表示式已經有了優先順序。 比較難懂的是

表示式(堆疊)

思路: (1)建立兩個字元型的堆疊和一個浮點數的堆疊 (2)先輸入表示式,將表示式轉換為字尾表示式 (3)求出字尾表示式的結果 #include<iostream> #include<cstring> #include<cstdio> using namesp

刁肥宅詳解中綴表示式問題:C++實現順序/鏈棧解決

       1. 表示式的種類        如何將表示式翻譯成能夠正確求值的指令序列,是語言處理程式要解決的基本問題,作為棧的應用事例,下面介紹表示式的求值過程。 任何一個表示式都是由

資料結構——表示式(程式碼)

表示式求值 C++ 環境codeblocks17 通過 /* 表示式求值,可用運算子 +-/*(){}[] @CGQ 2018/10/30 */ #include<stdio.h> #include<stdlib.h> #include<ctype.h>

C語言_解決括號匹配問題和逆波蘭表示式為題

##1、括號匹配問題: 解決思路: void MatchBrackets (const char* str) { char* per = NULL; int i = 0; Stack s; assert (str != NULL); InitStack (&s);

表示式--資料結構課本演算法實現

這篇部落格介紹的表示式求值是用C語言實現的,只使用了c++裡面的引用。 資料結構課本上的一個例題,但是看起來很簡單,實現卻遇到了很多問題。 這個題需要構建兩個棧,一個用來儲存運算子OPTR, 一個用來儲存數字OPND。 但是,數字和運算子都定義成字元型棧嗎? 出現了問題,當運算結果或中間結果為負時,沒

表示式-中序表示式轉換成後序表示式然後

/*表示式求值,先轉換成字尾表示式,再計算。 //從中綴表示式中從左往右依次取出資料 //如遇到運算元,直接輸出到字尾的佇列裡。 //如果遇到操作符(包括括號),這裡再定義一個存放操作符的棧,則: //i.如果操作符是'(',入棧 //ii.如果操作符是')',則把棧裡的操作符依次出棧並插入到字尾序

[學習筆記]多項式的整除取模多點和插及常係數線性遞推

一、開頭 ( WC2019 神犇協會) undefeatedKO : NOI2017 的題大家都 AK 了嗎? All : AK 了! ION :我們穿越到 2019 年的 WC 怎麼樣? olis :好啊!聽說一個弱雞 xyz32768 要來 WC ,我們一到就把他 D 一遍,這樣他

leet150. 逆波蘭表示式

題目: 求在 逆波蘭表示法 中算術表示式的值。 有效的運算子號包括 +, -, *, / 。每個運算物件可以是整數,也可以是另一個逆波蘭計數表達。 例如: ["2", "1", "+", "3", "*

表示式(exp) - 表示式 - 搜尋

題目大意: 給定n(=7)和s,以及一個表示式(僅有 a i

LeetCode:逆波蘭表示式【150】

LeetCode:逆波蘭表示式求值【150】 題目描述 根據逆波蘭表示法,求表示式的值。 有效的運算子包括 +, -, *, / 。每個運算物件可以是整數,也可以是另一個逆波蘭表示式。 說明: 整數除法只保留整數部分。 給定逆波蘭表示式總是

佇列&棧//逆波蘭表示式

根據逆波蘭表示法,求表示式的值。 有效的運算子包括 +, -, *, / 。每個運算物件可以是整數,也可以是另一個逆波蘭表示式。 說明: 整數除法只保留整數部分。 給定逆波蘭表示式總是有效的。換句話說,表示式總會得出有效數值且

棧的表示式---通過運算子的優先順序比較

#include <iostream> #include <stdlib.h> using namespace std; struct SqStack { char *base; char *top; }; int cmp[10][10] = { {'>','&g

表示式 NYOJ - 35

題解 題目連結 先將表示式字串去掉等號後使用逆波蘭規則轉換為字尾表示式然後計算 AC程式碼 #include <stdio.h> #include <bits/stdc++.h> using namespace std; typedef long long