《飛行大亨》是我很喜歡的一部電影,不過這里我想介紹的是一個叫Aviator的開源的Java表達式求值器。
一、輪子的必要性
表達式的求值上,java的選擇非常多,強大的如Groovy、JRuby,N年沒維護的beanshell,包括javaeye上朋友的IKExpression。為什么還需要Aviator?或者說Aviator的特點是什么?
我將Aviator定位在Groovy這樣全功能的腳本和IKExpression這樣的簡易的表達式求值之間的東西,如果你不希望帶上Groovy那么龐大的jar卻只用上一點點的功能,如果你希望功能和性能上比IKExpression好那么一些,那么也許你可以考慮Aviator。
Aviator的設計思路跟利用GroovyObject的求值是一樣,通過編譯并動態生成字節碼的方式將表達式編譯成一個類,然后反射執行這個類,因此會在效率上比純解釋執行的IKExpression好一些。

二、讓輪子轉起來。
求算術表達式:
import com.googlecode.aviator.AviatorEvaluator;



public class SimpleExample
{

public static void main(String[] args)
{
Long result = (Long) AviatorEvaluator.execute("1+2+3");
System.out.println(result);
}
}
執行入口統一為AviatorEvaluator類,它有一系列靜態方法。
邏輯表達式和關系運算:
AviatorEvaluator.execute("3>1 && 2!=4 || true");
Aviator支持所有的關系運算符和算術運算符,不支持位運算,同時支持表達式的優先級,優先級跟Java的運算符一樣,并且支持通過括號來強制優先級。
使用變量和字符串相加:
String yourname = “aviator”;
Map<String, Object> env = new HashMap<String, Object>();
env.put("yourname", yourname);
String result = (String) AviatorEvaluator.execute(" 'hello ' + yourname ", env);
System.out.println(result);
打印:
hello aviator
字符串可以單引號也可以雙引號括起來,并且支持轉義字符。變量名稱只要是合法的java identifer即可,變量需要用戶傳入,通過Map<String,Object>指定變量名和值是什么,這里的變量是yourname。
變量的訪問支持嵌套訪問,也就是dot操作符來訪問變量里的屬性,假設我們有一個Foo類:

public static class Foo
{
int i;
float f;
Date date = new Date();


public Foo(int i, float f, Date date)
{
super();
this.i = i;
this.f = f;
this.date = date;
}

public int getI()
{
return i;
}

public void setI(int i)
{
this.i = i;
}

public float getF()
{
return f;
}

public void setF(float f)
{
this.f = f;
}

public Date getDate()
{
return date;
}

public void setDate(Date date)
{
this.date = date;
}

}
然后在使用一個表達式來描述Foo里的各種屬性:
Foo foo = new Foo(100, 3.14f, new Date());
Map<String, Object> env = new HashMap<String, Object>();
env.put("foo", foo);

String result =
(String) AviatorEvaluator.execute(
" '[foo i='+ foo.i + ' f='+foo.f+' year='+(foo.date.year+1900)+ ' month='+foo.date.month +']' ",
env);
我們可以通過foo.date.year的方式來訪問變量foo中date屬性的year值,這是利用commons-beanutils的反射功能實現的,前提是你的變量是合法的JavaBean(public、getter缺一不可)。
三元表達式:
AviatorEvaluator.execute("3>0? 'yes':'no'");
上面都還是一個求值器表達式的常見功能,下面要描述的是Aviator的一些偏腳本性的功能。
類Ruby、Perl的正則匹配,匹配email地址:
AviatorEvaluator.execute("'killme2008'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ ");
成功的話返回true,否則返回false。//括起來的字符序列形成一個正則表達式Pattern類型,=~用于匹配,只能在String和Pattern之間使用。
匹配成功,獲得匹配的分組,利用變量$digit:
AviatorEvaluator.execute("'killme2008@gmail.com'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ ? $1:'unknow'");

匹配成功返回$1,表示第一個匹配的分組,也就是用戶名 killme2008
函數調用:
AviatorEvaluator.execute("sysdate()");
sysdate()是一個內置函數,返回當前日期,跟new java.util.Date()效果相同。
更多內置函數:
AviatorEvaluator.execute("string.length('hello')"); // 求字符串長度
AviatorEvaluator.execute("string.contains('hello','h')"); //判斷字符串是否包含字符串
AviatorEvaluator.execute("string.startsWith('hello','h')"); //是否以子串開頭
AviatorEvaluator.execute("string.endsWith('hello','llo')"); 是否以子串結尾

AviatorEvaluator.execute("math.pow(-3,2)"); // 求n次方
AviatorEvaluator.execute("math.sqrt(14.0)"); //開平方根
AviatorEvaluator.execute("math.sin(20)"); //正弦函數

可以看到Aviator的函數調用風格非常類似lua或者c。
自定義函數,實現AviatorFunction接口并注冊即可,比如我們實現一個add函數用于相加:
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorDouble;
import com.googlecode.aviator.runtime.type.AviatorFunction;
import com.googlecode.aviator.runtime.type.AviatorObject;

class AddFunction implements AviatorFunction
{


public AviatorObject call(Map<String, Object> env, AviatorObject
args)
{

if (args.length != 2)
{
throw new IllegalArgumentException("Add only supports two arguments");
}
Number left = FunctionUtils.getNumberValue(0, args, env);
Number right = FunctionUtils.getNumberValue(1, args, env);
return new AviatorDouble(left.doubleValue() + right.doubleValue());
}



public String getName()
{
return "add";
}

}

注冊并調用:
AviatorEvaluator.addFunction(new AddFunction());
System.out.println(AviatorEvaluator.execute("add(1,2)"));
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;
boolean bool=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 |
原始的測試報告在這里。
四、結語
能看到這里,并且感興趣的朋友請點擊項目主頁:
http://code.google.com/p/aviator/
下載地址:
http://code.google.com/p/aviator/downloads/list
完整的用戶手冊:
http://code.google.com/p/aviator/wiki/User_Guide_zh
目前版本仍然是1.0.0-RC,希望更多朋友試用并最終release。有什么疑問或者建議請跟貼。