论坛首页 Java企业应用论坛

FEL(表达式语言)0.4.1版本发布(没有最快,只有更快)

浏览 13180 次
精华帖 (0) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-08-27   最后修改:2011-09-14

更新日志
2011-08-30:发布0.3.1版。解决在Tomcat下不能编译的问题
2011-08-30:发布0.3.2版。重构代码,加入编译优化功能。
2011-09-4:发布0.4版。全面支持函数、自定义生成源码、再次提升性能。
2011-09-12:发布0.4.1版。实现自适应编译器,在java1.5环境下,使用1.5的编译器(需要将tools.jar加入classpath)。在java1.6的环境下,使用1.6的编译器。


简介
Fel是简单的、高效的、易扩展的开源项目,使用java(jdk1.5)开发的开源项目。与java紧密结合,可以在Fel表达式中调用java方法(如:"abc".length(),"abc".substring(1)等)。
简单性:语法简单,与java表达式类似,支持中文变量和函数。其实现原理也非常简单,只要稍微有一些 java基础就可以看懂源码。
高效性:在使用编译模式的性能,基本上java执行表达式的性能相同。
易扩展:在执行表达式的主要环节(解析表达式、编译表达式、函数管理、变量管理)都有很高的可定制性。
Fel适合处理海量数据。它是功能强大的、实现简单、扩展性强的表达式语言。通过Fel表达式语言的实现原理创建一个新的表达式语言也不是难事。

快速入门
获取Fel
项目地址:http://code.google.com/p/fast-el/
源代码:http://fast-el.googlecode.com/svn/trunk/ 
安装部署
将fel-all-xx.jar加入classpath就可以使用。
如果需要使用编译功能,还需要加入tools.jar到classpath

功能
HelloWorld
执行四则表达式,代码如下所示:

FelEngine fel = new FelEngineImpl();
Object result = fel.eval("5000*12+7500");
System.out.println(result);

输出结果:67500

变量
使用变量,其代码如下所示:
FelContext ctx = fel.getContext();
ctx.set("单价", 5000);
ctx.set("数量", 12);
ctx.set("运费", 7500);
Object result = fel.eval("单价*数量+运费");
System.out.println(result);

输出结果:67500

调用JAVA方法
FelEngine fel = new FelEngineImpl();
FelContext ctx = fel.getContext();
ctx.set("out", System.out);
fel.eval("out.println('Hello Everybody'.substring(6))");

输出结果:Everybody

自定义上下文环境
//负责提供气象服务的上下文环境
FelContext ctx = new AbstractConetxt() {
	public Object get(Object name) {
		if("天气".equals(name)){
			return "晴";
		}
		if("温度".equals(name)){
			return 25;
		}
		return null;
	}
};
FelEngine fel = new FelEngineImpl(ctx);
Object eval = fel.eval("'天气:'+天气+';温度:'+温度");
System.out.println(eval);

输出结果:天气:晴;温度:25

多层上下文环境(命名空间)
FelEngine fel = new FelEngineImpl();
String costStr = "成本";
String priceStr="价格";
FelContext baseCtx = fel.getContext();
//父级上下文中设置成本和价格
baseCtx.set(costStr, 50);
baseCtx.set(priceStr,100);

String exp = priceStr+"-"+costStr;
Object baseCost = fel.eval(exp);
System.out.println("期望利润:" + baseCost);

FelContext ctx = new ContextChain(baseCtx, new MapContext());
//通货膨胀导致成本增加(子级上下文 中设置成本,会覆盖父级上下文中的成本)
ctx.set(costStr,50+20 );
Object allCost = fel.eval(exp, ctx);
System.out.println("实际利润:" + allCost);

输出结果:
期望利润:50
实际利润:30

编译执行
FelEngine fel = new FelEngineImpl();
FelContext ctx = fel.getContext();
ctx.set("单价", 5000);
ctx.set("数量", 12);
ctx.set("运费", 7500);
Expression exp = fel.compile("单价*数量+运费",ctx);
Object result = exp.eval(ctx);
System.out.println(result);

执行结果:67500
备注:适合处理海量数据,编译执行的速度基本与Java字节码执行速度一样快。

自定义函数
	public static void userFunction(){
		//定义hello函数
		Function fun = new CommonFunction() {

			public String getName() {
				return "hello";
			}
			
			/* 
			 * 调用hello("xxx")时执行的代码
			 */
			@Override
			public Object call(Object[] arguments) {
				Object msg = null;
				if(arguments!= null && arguments.length>0){
					msg = arguments[0];
				}
				return ObjectUtils.toString(msg);
			}

		};
		FelEngine e = new FelEngineImpl();
		//添加函数到引擎中。
		e.addFun(fun);
		String exp = "hello('fel')";
		//解释执行
		Object eval = e.eval(exp);
		System.out.println("hello "+eval);
		//编译执行
		Expression compile = e.compile(exp, null);
		eval = compile.eval(null);
		System.out.println("hello "+eval);
	}

执行结果:
hello fel
hello fel

操作符重载
/*
		 * 扩展Fel的+运算符,使其支持数组+数组
		 */
		
		FelEngine fel = new FelEngineImpl();
		// 单价
		double[] price = new double[] { 2, 3, 4 };
		// 费用
		double[] cost = new double[] { 0.3, 0.3, 0.4 };
		FelContext ctx = fel.getContext();
		ctx.set("单价", price);
		ctx.set("费用", cost);
		String exp = "单价+费用";
		InteOpt interpreters = new InteOpt();
		//定义"+"操作符的解释方法。
		interpreters.add("+", new Interpreter() {
			public Object interpret(FelContext context, FelNode node) {
				List<FelNode> args = node.getChildren();
				double[] leftArg = (double[]) args.get(0).eval(context);
				double[] rightArg = (double[]) args.get(1).eval(context);
				return sum(leftArg)+sum(rightArg);
			}
			//对数组进行求和
			public double sum(double[] array){
				double d = 0;
				for (int i = 0; i < array.length; i++) {
					d+=array[i];
				}
				return d;
			}
		});
		
		//使用自定义解释器作为编译选项进行进行编译
		Expression expObj = fel.compile(exp, null, interpreters);
		Object eval = expObj.eval(ctx);
		System.out.println("数组相加:"+eval)

执行结果:数组相加:10.0

附件fel最新版本和示例代码
未完待续
   发表时间:2011-08-27   最后修改:2011-09-05
性能测试
选用Jexl作为比较对象是对不住Jexl了。在Fel出现之前,使用的是Jexl,对它还是有感情的(Fel部分模块也借鉴了Jexl)。
Jexl毕竟没有将表达式编译成字节码,所以效率不是非常高,对比测试也有失公平。
当然,Fel在性能上并不是专找软柿子比较。
目前还没有发现开源的EL比Fel快。如果有,请告诉我,我也学习一下。

以下所有测试中的时间统计都没有包含编译时间
测试环境:
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_22-b0Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_22-b03, mixed mode)
名称 版本
操作系统 Microsoft Windows 7 Home Basic SP1 64bit
CPU Intel(R) Core(TM) i3-2310M CPU@2.10GHz
内存 4G ddr3-1333
JDK java version "1.5.0_22"


上下文环境
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("i", 100);
vars.put("pi", 3.14d);
vars.put("d", -3.9);
vars.put("b", (byte) 4);
vars.put("bool", false);
Map<String,Object> m = new HashMap<String, Object>();
m.put("d", 5);
m.put(null,"this is null");
vars.put("m", m);
vars.put("s","hello world");
测试场景1:常量计算
表达式:1000+100.0*99-(600-3*15)%(((68-9)-3)*2-100)+10000%7*71
执行结果:11181.0
EL 速度(ms)
fel 62
jexl 57131


测试场景2:包含多个变量的布尔表达式
表达式: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
执行结果:true
EL 速度(ms)
fel 1405
jexl 46648

测试场景3:包含多个变量的算术表达式
表达式:pi*d+b-(1000-d*b/pi)/(pi+99-i*d)-i*pi*d/b
执行结果:295.86196287059323
EL 速度(ms)
fel_0.41419
jexl 21941


测试场景4:调用JAVA方法
表达式:s.substring(m.d)
执行结果: world
EL 速度(ms)
fel 686
jexl 15455


测试场景5:嵌套调用JAVA方法
表达式:s.substring(1).substring(2).indexOf('world')
执行结果:3
EL 速度(ms)
fel 861
jexl 19260


测试结论:

常量表达式:比Jexl快得太多了,主要是Fel对常量表达式作了优化,。
包含多个变量的表达式:Fel比Jexl快10倍甚至几十倍。Fel同样对变量访问作了优化。
字符串访问:Fel还是快20倍以上。


并发测试
测试环境概述:由于数据量比较大,加了内存参数 -Xmx1024M
测试线程数:50,100,200;
每线程执行表达式次数:100万
Jexl落败是意料之中的,不过,没有编译成字节码的Jexl执行速度有这么快,已经很不错了。
执行的表达式:
"1000+100.0*99-(600-3*15)%(((68-9)-3)*2-100)+10000%7*71";
 "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 ";
"pi*d+b-(1000-d*b/pi)/(pi+99-i*d)-i*pi*d/b";
"pi*d+b-(1000-d*b/pi)/(pi+99-i*d)-i*pi*d/b";
 "'hello world'.substring(1).substring(2).indexOf('world')";


其中产生变量值的方式(使用自定义context)
	final FelContext ctx = new AbstractConetxt() {
			int index = 0;
			int size = 10;
			Object[] vars = new Object[size];
			{
				for (int i = 0; i < vars.length; i++) {
					vars[i] = Math.random() * 100;
				}
			}
//提供变量
			public Object get(Object name) {
				return vars[index++ % size];
			}
	};


线程数 总耗时(ms) 每个线程的平均耗时(ms)
5016574294.18
10029225344.32
20066353562.005


并发测试总结:即使在并发的情况下,Fel执行表达式500万次,平均耗时也在1秒之内。



0 请登录后投票
   发表时间:2011-08-27  
先睡了,静待意见建议。
0 请登录后投票
   发表时间:2011-08-28  
期待高并发下的测试,没有并发的测试意义不大
0 请登录后投票
   发表时间:2011-08-28   最后修改:2011-08-28
另外jexl2是支持jsr223的,这个现在是什么情况
0 请登录后投票
   发表时间:2011-08-28  
另外,期待支持脚本的版本
0 请登录后投票
   发表时间:2011-08-28  
freish 写道
期待高并发下的测试,没有并发的测试意义不大

说得不错,请说明一下具体的测试场景,我再测一下。

freish 写道
另外jexl2是支持jsr223的,这个现在是什么情况

没有详细研究JSR223,有空再看一下。目前正在处理扩展性和性能等问题

freish 写道
另外,期待支持脚本的版本

其实也考虑过做支持脚本的版本,由于时间有限,还是先把表达式的版本做好做精吧。
目标是让FEL成可易扩展和维护的效率极高的表达式语言。

最后,谢谢你的回复。
0 请登录后投票
   发表时间:2011-08-28  
担心性能的问题。
看起来挺好用  顶楼主
0 请登录后投票
   发表时间:2011-08-28  
呵呵,也用了antlr ,有兴趣可以加入

61765717 群,专门讨论antlr 的地方

http://beetl.sourceforge.net/ 我写的模板语言,也用了antlr
0 请登录后投票
   发表时间:2011-08-28  
应用场景是什么呢?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics