aviator——让表达式飞起来 -凯发k8网页登录

生活、程序、未来
   :: 凯发k8网页登录首页 ::  ::  :: 聚合  :: 管理
posted on 2010-06-29 11:44 dennis 阅读(20917) 评论(15)     所属分类: javamy open-source

    《》是我很喜欢的一部电影,不过这里我想介绍的是一个叫aviator的开源的java表达式求值器。

一、轮子的必要性

    表达式的求值上,java的选择非常多,强大的如groovy、jruby,n年没维护的beanshell,包括javaeye上朋友的ikexpression。为什么还需要aviator?或者说aviator的特点是什么?

    我将aviator定位在groovy这样全功能的脚本和ikexpression这样的简易的表达式求值之间的东西,如果你不希望带上groovy那么庞大的jar却只用上一点点的功能,如果你希望功能和性能上比ikexpression好那么一些,那么也许你可以考虑aviator。

    aviator的设计思路跟利用groovyobject的求值是一样,通过编译并动态生成字节码的方式将表达式编译成一个类,然后反射执行这个类,因此会在效率上比纯解释执行的ikexpression好一些。

 aviator结构图

二、让轮子转起来。

求算术表达式: 

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(1003.14fnew 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个变量的表达式: 

* pi  (d * b - 199/ (1 - d * pi) - (2  100 - i / pi) % 99 ==* 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 = (byte4;
        
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。有什么疑问或者建议请跟贴。

 

 

 


评论

# re: aviator——让表达式飞起来  回复     

2010-06-29 16:48 by
大虾,这个是你写的吗,你怎么这么有时间啊,拜读源码.

# re: aviator——让表达式飞起来  回复  更多评论   

2010-06-29 18:00 by dennis
@jaedong
是我的作品,感谢关注。

# re: aviator——让表达式飞起来  回复     

2010-06-29 19:16 by
nice~~i like it

# re: aviator——让表达式飞起来  回复     

2010-06-29 20:56 by
支持 jdk 1.4?

# re: aviator——让表达式飞起来  回复     

2010-06-29 21:00 by
@guest

oh my gosh, no support for jdk 1.4...

# re: aviator——让表达式飞起来  回复     

2010-06-29 22:54 by 隔叶黄莺
很不错的,以后我想一定也用得着的。

# re: aviator——让表达式飞起来  回复     

2010-06-30 09:20 by @joe
java的scriptengine同样可以做到。。。。

# re: aviator——让表达式飞起来  回复     

2010-06-30 09:20 by @joe
java的scriptengine同样可以做到。。。。

# re: aviator——让表达式飞起来  回复     

2010-06-30 10:13 by
用spring expression language就是了(比这个还要更强大),为什么还要用这个???

# re: aviator——让表达式飞起来  回复  更多评论   

2010-06-30 10:22 by dennis
@lancelot
恩,spring el是个选择,我已经提到,avaitor的定位是 spring el,groovy这样强大的脚本语言与ikexpression这样的简易引擎之间的东西。强大的东西很多功能你用不上,简易的又不能满足要求,这种时候也许可以考虑下aviator。

# re: aviator——让表达式飞起来  回复  更多评论   

2010-06-30 10:23 by dennis
@@joe
script engine的调用也是委托给脚本语言,script engine的性能不怎么样,已有的几个实现来说。如果你用在生产环境的话,基本不靠谱,自己玩玩还行。

# re: aviator——让表达式飞起来[未登录]  回复     

2011-03-24 18:17 by
mvel也是一款表达式解析器,挺轻量级的

# re: aviator——让表达式飞起来  回复     

2011-07-11 00:28 by
很好用,刚成功应用到一个之前做的项目中,反应良好,继续关注

# re: aviator——让表达式飞起来  回复     

2011-09-27 00:50 by
你好,请问aviator能否将execute过程中匹配的变量名字及其值获取?例如你的userguide的例子:
public static void main(string[] args) {
string expression = "a-(b-c)>100"; // 编译表达式
expression compiledexp = aviatorevaluator.compile(expression); map env = new hashmap();
env.put("a", 100.3);
env.put("b", 45);
env.put("c", -199.100);
// 执行表达式
boolean result = (boolean) compiledexp.execute(env); system.out.println(result);
}

能否获知到底是a、b还是a、c这个变量被使用了,它们的值是多少?多谢
saiky.liu#gmail.com

# re: aviator——让表达式飞起来  回复     

2016-07-27 22:35 by
很好用,刚用到最近的一个项目中

只有注册用户后才能发表评论。


网站导航:
              
相关文章:
 
网站地图