posted on 2010-06-29 11:44
dennis 阅读(20917)
评论(15) 所属分类:
java 、
my open-source
《》是我很喜欢的一部电影,不过这里我想介绍的是一个叫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指定变量名和值是什么,这里的变量是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:
算术表达式 1000100.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?45: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 |
原始的测试报告在。
四、结语
能看到这里,并且感兴趣的朋友请点击项目凯发k8网页登录主页:
下载地址:
完整的用户手册:
目前版本仍然是1.0.0-rc,希望更多朋友试用并最终release。有什么疑问或者建议请跟贴。