预测算法用java实现 - Knowledge&&Imagination - ITeye技术网站
一,简易平均法,是一种简便的时间序列法。是以一定观察期的数据求得平均数,并以所求平均数为基础,预测未来时期的预测值。简易平均法是最简单的定量预测方法。简易平均法的运算过程简单,不需要进行复杂的模型设计和数学运用,常在市场的近期预测、短期预测中使用。
1、算术平均法
算术平均法,就是以观察期数据之和除以求和时使用的数据个数(或资料期数),求得平均数进行预测的方法。
运用算术平均法求平均数,有两种形式:
(一)以最后一年的每月平均值或数年的每月平均值,作为次年的每月预测值
为了确定合理的误差,用公式估计出预测的标准差。
按公式计算某种可靠程度要求时的预测区间。
(二)以观察期的每月平均值作为预测期对应月份的预测值
当时间序列资料在年度内变动显著或呈季节性变化时,用第一种方法求平均值进行预测的话,势必影响预测值的精确度,同时也不能反映出年度内不同月、季的情况。
2、几何平均法
几何平均法,就是运用几何平均数求出预测目标的发展速度。
几何平均数,就是将观察期n个环比速度资料数相乘,开n次方,所得的n次方根。
根据几何平均数建立预测模型进行预测。
3、加权平均法
加权平均法,就是在求平均数时,根据观察期各资料重要性的不同,分别给以不同的权数后加以平均的方法。
其特点是:所求得的平均数,已包含了长期趋势变动。
二,移动平均法
移动平均法是用一组最近的实际数据值来预测未来一期或几期内公司产品的需求量、公司产能等的一种常用方法。移动平均法适用于即期预测。当产品需求既不快速增长也不快速下降,且不存在季节性因素时,移动平均法能有效地消除预测中的随机波动,是非常有用的。移动平均法根据预测时使用的各元素的权重不同
移动平均法是一种简单平滑预测技术,它的基本思想是:根据时间序列资料、逐项推移,依次计算包含一定项数的序时平均值,以反映长期趋势的方法。因此,当时间序列的数值由于受周期变动和随机波动的影响,起伏较大,不易显示出事件的发展趋势时,使用移动平均法可以消除这些因素的影响,显示出事件的发展方向与趋势(即趋势线),然后依趋势线分析预测序列的长期趋势。
移动平均法的种类
移动平均法可以分为:简单移动平均和加权移动平均。
1、简单移动平均法
简单移动平均的各元素的权重都相等。简单的移动平均的计算公式如下: Ft=(At-1+At-2+At-3+…+At-n)/n式中,
•Ft--对下一期的预测值;
•n--移动平均的时期个数;
•At-1--前期实际值;
•At-2,At-3和At-n分别表示前两期、前三期直至前n期的实际值。
2、加权移动平均法
加权移动平均给固定跨越期限内的每个变量值以不同的权重。其原理是:历史各期产品需求的数据信息对预测未来期内的需求量的作用是不一样的。除了以n为周期的周期性变化外,远离目标期的变量值的影响力相对较低,故应给予较低的权重。 加权移动平均法的计算公式如下:
Ft=w1At-1+w2At-2+w3At-3+…+wnAt-n式中,
•w1--第t-1期实际销售额的权重;
•w2--第t-2期实际销售额的权重;
•wn--第t-n期实际销售额的权
•n--预测的时期数;w1+ w2+…+ wn=1
在运用加权平均法时,权重的选择是一个应该注意的问题。经验法和试算法是选择权重的最简单的方法。一般而言,最近期的数据最能预示未来的情况,因而权重应大些。例如,根据前一个月的利润和生产能力比起根据前几个月能更好的估测下个月的利润和生产能力。但是,如果数据是季节性的,则权重也应是季节性的。
移动平均法的优缺点
使用移动平均法进行预测能平滑掉需求的突然波动对预测结果的影响。但移动平均法运用时也存在着如下问题:
1、 加大移动平均法的期数(即加大n值)会使平滑波动效果更好,但会使预测值对数据实际变动更不敏感;
2、 移动平均值并不能总是很好地反映出趋势。由于是平均值,预测值总是停留在过去的水平上而无法预计会导致将来更高或更低的波动;
3、 移动平均法要由大量的过去数据的记录。
什么是指数平滑法
指数平滑法是布朗(Robert G..Brown)所提出,布朗(Robert G..Brown)认为时间序列的态势具有稳定性或规则性,所以时间序列可被合理地顺势推延;他认为最近的过去态势,在某种程度上会持续到最近的未来,所以将较大的权数放在最近的资料。
三,指数平均法
指数平滑法是生产预测中常用的一种方法。也用于中短期经济发展趋势预测,所有预测方法中,指数平滑是用得最多的一种。简单的全期平均法是对时间数列的过去数据一个不漏地全部加以同等利用;移动平均法则不考虑较远期的数据,并在加权移动平均法中给予近期资料更大的权重;而指数平滑法则兼容了全期平均和移动平均所长,不舍弃过去的数据,但是仅给予逐渐减弱的影响程度,即随着数据的远离,赋予逐渐收敛为零的权数。
也就是说指数平滑法是在移动平均法基础上发展起来的一种时间序列分析预测法,它是通过计算指数平滑值,配合一定的时间序列预测模型对现象的未来进行预测。其原理是任一期的指数平滑值都是本期实际观察值与前一期指数平滑值的加权平均。
指数平滑法的基本公式是:St=ayt+(1-a)St-1 式中,
•St--时间t的平滑值;
•yt--时间t的实际值;
•St − 1--时间t-1的平滑值;
•a--平滑常数,其取值范围为[0,1];
由该公式可知:
1.St是yt和 St − 1的加权算数平均数,随着a取值的大小变化,决定yt和 St − 1对St的影响程度,当a取1时,St = yt;当a取0时,St = St − 1。
2.St具有逐期追溯性质,可探源至St − t + 1为止,包括全部数据。其过程中,平滑常数以指数形式递减,故称之为指数平滑法。指数平滑常数取值至关重要。平滑常数决定了平滑水平以及对预测值与实际结果之间差异的响应速度。平滑常数a越接近于1,远期实际值对本期平滑值影响程度的下降越迅速;平滑常数a越接近于 0,远期实际值对本期平滑值影响程度的下降越缓慢。由此,当时间数列相对平稳时,可取较大的a;当时间数列波动较大时,应取较小的a,以不忽略远期实际值的影响。生产预测中,平滑常数的值取决于产品本身和管理者对良好响应率内涵的理解。
3.尽管St包含有全期数据的影响,但实际计算时,仅需要两个数值,即yt和 St − 1,再加上一个常数a,这就使指数滑动平均具逐期递推性质,从而给预测带来了极大的方便。
4.根据公式S1=ay1+(1-a)S0,当欲用指数平滑法时才开始收集数据,则不存在y0。无从产生S0,自然无法据指数平滑公式求出S1,指数平滑法定义S1为初始值。初始值的确定也是指数平滑过程的一个重要条件。
如果能够找到y1以前的历史资料,那么,初始值S1的确定是不成问题的。数据较少时可用全期平均、移动平均法;数据较多时,可用最小二乘法。但不能使用指数平滑法本身确定初始值,因为数据必会枯竭。
如果仅有从y1开始的数据,那么确定初始值的方法有:
1)取S1等于y1;
2)待积累若干数据后,取S1等于前面若干数据的简单算术平均数,如:S1=(y1+ y2+y3)/3等等。
指数平滑的预测公式
据平滑次数不同,指数平滑法分为:一次指数平滑法、二次指数平滑法和三次指数平滑法等。
(一) 一次指数平滑预测
当时间数列无明显的趋势变化,可用一次指数平滑预测。其预测公式为:
yt+1'=ayt+(1-a)yt' 式中,
•yt+1'--t+1期的预测值,即本期(t期)的平滑值St ;
•yt--t期的实际值;
•yt'--t期的预测值,即上期的平滑值St-1 。
该公式又可以写作:yt+1'=yt'+a(yt- yt')。可见,下期预测值又是本期预测值与以a为折扣的本期实际值与预测值误差之和。
(二) 二次指数平滑预测
二次指数平滑是对一次指数平滑的再平滑。它适用于具线性趋势的时间数列。其预测公式为:
yt+m=(2+am/(1-a))yt'-(1+am/(1-a))yt=(2yt'-yt)+m(yt'-yt) a/(1-a)
式中,yt= ayt-1'+(1-a)yt-1
显然,二次指数平滑是一直线方程,其截距为:(2yt'-yt),斜率为:(yt'-yt) a/(1-a),自变量为预测天数。
(三) 三次指数平滑预测
三次指数平滑预测是二次平滑基础上的再平滑。其预测公式是:
yt+m=(3yt'-3yt+yt)+[(6-5a)yt'-(10-8a)yt+(4-3a)yt]*am/2(1-a)2+ (yt'-2yt+yt')*a2m2/2(1-a)2
式中,yt=ayt-1+(1-a)yt-1
它们的基本思想都是:预测值是以前观测值的加权和,且对不同的数据给予不同的权,新数据给较大的权,旧数据给较小的权。
[编辑]指数平滑法的趋势调整
一段时间内收集到的数据所呈现的上升或下降趋势将导致指数预测滞后于实际需求。通过趋势调整,添加趋势修正值,可以在一定程度上改进指数平滑预测结果。调整后的指数平滑法的公式为:
包含趋势预测(YITt)=新预测(Yt)+趋势校正(Tt)
进行趋势调整的指数平滑预测有三个步骤:
1、 利用前面介绍的方法计算第t期的简单指数平滑预测(Yt);
2、 计算趋势。其公式为: Tt=(1-b)Tt-1+b(Yt-Yt-1)其中,
•Tt=第t期经过平滑的趋势;
•Tt-1=第t期上期经过平滑的趋势;
•b=选择的趋势平滑系数;
•Yt=对第t期简单指数平滑预测;
•Yt-1=对第t期上期简单指数平滑预测。
3、计算趋势调整后的指数平滑预测值(YITt)。计算公式为:YITt=Yt+Tt。
四,线性回归法
1. 一元线性回归预测模型
一元线性回归预测法是分析一个因变量与一个自变量之间的线性关系的预测方法。 常用统计指标:平均数、增减量、平均增减量。
确定直线的方法是最小二乘法 最小二乘法的基本思想:最有代表性的直线应该是直线到各点的距离最近。然后用这条直线进行预测。
1、选取一元线性回归模型的变量 ;
2、绘制计算表和拟合散点图 ;
3、计算变量间的回归系数及其相关的显著性 ;
4、回归分析结果的应用 。
、经济意义检验:就是根据模型中各个参数的经济含义,分析各参数的值是否与分析对象的经济含义相符。
2、回归标准差检验
3、拟合优度检验
4、回归系数的显著性检验
利用回归预测模型进行预测
可以分为:点预测和置信区间预测法
1、点预测法:将自变量取值带入回归预测模型求出因变量的预测值。
2、置信区间预测法:估计一个范围,并确定该范围出现的概率。置信区间的大小的影响的因素:a、因变量估计值;b、回归标准差;C、概率度t。
模型分析
一元线性回归分析预测法,是根据自变量x和因变量Y的相关关系,建立x与Y的线性回归方程进行预测的方法。由于市场现象一般是受多种因素的影响,而并不是仅仅受一个因素的影响。所以应用一元线性回归分析预测法,必须对影响市场现象的多种因素做全面分析。只有当诸多的影响因素中,确实存在一个对因变量影响作用明显高于其他因素的变量,才能将它作为自变量,应用一元相关回归分析市场预测法进行预测。一元线性回归分析法的预测模型为:yt=b+axt 式中,xt代表t期自变量的值;yt代表t期因变量的值; a、b代表一元线性回归方程的参数。 a、b参数由下列公式求得(用代表): 为简便计算,我们作以下定义: (2) 式中: 这样定义a、b后,参数由下列公式求得: 将a、b代入一元线性回归方程Yt = a + bxt,就可以建立预测模型,那么,只要给定xt值,即可求出预测值。 在回归分析预测法中,需要对X、Y之间相关程度作出判断,这就要计算相关系数Y,其公式如下: 相关系数r的特征有: ①相关系数取值范围为:-1≤r≤1 。 ②r与b符合相同。当r>0,称正线性相关,Xi上升,Yi呈线性增加。当r<0,称负线性相关,Xi上升,Yi呈线性减少。 ③|r|=0,X与Y无线性相关关系;|r|=1,完全确定的线性相关关系;0<|r|<1,X与Y存在一定的线性相关关系;|r|>0.7,为高度线性相关;0.3<|r|≤0.7,为中度线性相关;|r|≤0.3,为低度线性相关.
Linux上Oracle 11g安装步骤图解 - gaojun - 博客园
另,可参考:
Redhat Linux安装Oracle 11g R2数据库
1、检查硬件
内存
# grep MemTotal /proc/meminfo
交换空间
# grep SwapTotal /proc/meminfo
磁盘空间
# df -ah
2、下载软件
Oracle Database 10g Release 2 (10.2.0.1) Software下载位置:
http://otn.oracle.com/software/products/database/oracle10g/index.html
3、修改host文件
查看/etc/hosts文件中必须包含a fully qualified name for the server
4、安装软件包检查
1、检查下列包是否安装,若未安装则要先安装:
binutils-2.17.50.0.6-2.el5
compat-libstdc++-33-3.2.3-61
elfutils-libelf-0.125-3.el5
elfutils-libelf-devel-0.125
glibc-2.5-12
glibc-common-2.5-12
glibc-devel-2.5-12
gcc-4.1.1-52
gcc-c++-4.1.1-52
libaio-0.3.106
libaio-devel-0.3.106
libgcc-4.1.1-52
libstdc++-4.1.1
libstdc++-devel-4.1.1-52.e15
make-3.81-1.1
sysstat-7.0.0
unixODBC-2.2.11
unixODBC-devel-2.2.11
# rpm -qa | grep make gcc glibc compat openmotif21 setarch 等等
5、libstdc包的链接
(老版本需求,新版本oracle11不要设置):
ln -s /usr/lib/libstdc++.so.6.0.3 /usr/lib/libstdc++.so.5
#检测存在如果不做此链接,在安装过程中建好数据库,将会出现不能链接不上数据库问题。
三.参数设置
1、设置核心参数
在/etc/sysctl.conf文件中加入下列行:
#kernel.core_uses_pid = 1
kernel.shmall = 2097152
kernel.shmmax = 2147483648 -- (以字节为单位,物理内存数量*1024*1024*2,为内存的2倍)
kernel.shmmni = 4096
# semaphores: semmsl, semmns, semopm, semmni
kernel.sem = 250 32000 100 128
fs.file-max = 65536
net.ipv4.ip_local_port_range = 1024 65000
net.core.rmem_default=8388608
net.core.rmem_max=8388608
net.core.wmem_default=262144
net.core.wmem_max=262144
esc :wq!
# sysctl -p #运行下列命令改变核心参数
2、设置Shell Limits(系统资源限制),提高软件的运行效率
a.在/etc/security/limits.conf文件中加入下列红色行:
oracle soft nofile 65536
oracle hard nofile 65536
oracle soft nproc 16384
oracle hard nproc 16384
b.在/etc/pam.d/login文件中加入下列行,如果里面没有的话:
session required /lib/security/pam_limits.so
session required pam_limits.so
c.在/etc/profile后加入以下语句:
vi /etc/profile
if [ $USER = "oracle" ]; then
if [ $SHELL = "/bin/ksh" ]; then
ulimit -p 16384
ulimit -n 65536
else
ulimit -u 16384 -n 65536
fi
fi
3.创建用户和组及相关目录
--创建dba/oper/oinstall组
/usr/sbin/groupadd dba
/usr/sbin/groupadd oper
/usr/sbin/groupadd oinstall
--创建oracle用户并设置用户所属组
/usr/sbin/usermod -g oinstall -G dba oracle
/usr/sbin/useradd -g oinstall -G dba oracle
passwd oracle
# id oracle
uid=501(oracle) gid=501(oinstall) groups=501(oinstall),502(dba)
--创建相关安装目录
mkdir -p /opt/oracle/product
mkdir -p /opt/oracle/product/OraHome
mkdir -p /opt/oraInventory #(the default inventory folder)
mkdir -p /opt/oracle/oradata #(change the right file owner)
mkdir -p /var/opt/oracle
--设置目录所有者和权限
chown -R oracle.oinstall /opt/oracle
chown -R oracle.oinstall /opt/oracle/oradata
chown -R oracle.oinstall /opt/oracle/product/OraHome
chown -R oracle.dba /opt/oraInventory
chown oracle.dba /var/opt/oracle
chmod -R 775 /opt/oracle
chmod -R 755 /var/opt/oracle
4.设置安装环境变量
# su - oracle --su到oracle用户下,
更改用户的一些配置
$vi .bash_profile
添加以下参数,主要是配置oracle软件运环境参数
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
export ORACLE_BASE=/opt/oracle
export ORACLE_HOME=$ORACLE_BASE/product/OraHome
export ORACLE_SID=orcl
export ORACLE_OWNER=oracle
export ORACLE_TERM=vt100
export PATH=$PATH:$ORACLE_HOME/bin:$HOME/bin
export PATH=$ORACLE_HOME/bin:$ORACLE_HOME/Apache/Apache/bin:$PATH
LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib:/usr/local/lib
export LD_LIBRARY_PATH
CLASSPATH=$ORACLE_HOME/JRE:$ORACLE_HOME/jlib:$ORACLE_HOME/rdbms/jlib
CLASSPATH=$CLASSPATH:$ORACLE_HOME/network/jlib
export CLASSPATH
注:
11g:ORA_NLS33=$ORACLE_HOME/nls/admin/data
10g:ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data
9i:ORA_NLS33=/oracle/app/ora92/ocommon/nls/admin/data
保存退出
--执行以下命令让配置马上生效或以oracle用户登录使设置生效
$ source $HOME/.bash_profile
Silent Oracle 11g R2 install using only the command line | Beyond Oracle
1. Add Oracle public yum repository.
[root@localhost ~]$ cd /etc/yum.repos.d [root@localhost ~]$ wget http://public-yum.oracle.com/public-yum-el5.repo
# edit the file public-yum-el5.repo and enable both the [el5_u5_base] and the [ol5_u5_base] repositories in the yum configuration file by changing enable=0 to enable=1 in those sections
2. Install Oracle validated packages
[root@localhost ~]$ yum install oracle-validated
3. Download and Install Oracle software on command line
[root@localhost ~]$ su - oracle [oracle@localhost ~]$ cd /software/11gr2/database [oracle@localhost database]$ ./runInstaller -silent -force \ FROM_LOCATION=/software/11gr2/database/stage/products.xml \ oracle.install.option=INSTALL_DB_SWONLY \ UNIX_GROUP_NAME=oinstall \ INVENTORY_LOCATION=/home/oracle/oraInventory \ ORACLE_HOME=/u01/app/oracle/product/11.2/db_1 \ ORACLE_HOME_NAME="OraDb11g_Home1" \ ORACLE_BASE=/u01/app/oracle \ oracle.install.db.InstallEdition=SE \ oracle.install.db.isCustomInstall=false \ oracle.install.db.DBA_GROUP=dba \ oracle.install.db.OPER_GROUP=dba \ DECLINE_SECURITY_UPDATES=true [oracle@localhost database]$ exit # As a root user, execute the following script(s): [root@localhost ~]$ /home/oracle/oraInventory/orainstRoot.sh [root@localhost ~]$ /u01/app/oracle/product/11.2/db_1/root.sh
linux下IPTABLES配置详解 - 草原和大树 - 博客园
Chain INPUT (policy ACCEPT)
target prot opt source destination
target prot opt source destination
target prot opt source destination
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmp type 255
ACCEPT esp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT ah -- 0.0.0.0/0 0.0.0.0/0
ACCEPT udp -- 0.0.0.0/0 224.0.0.251 udp dpt:5353
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:631
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:80
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:25
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
可以看出我在安装linux时,选择了有防火墙,并且开放了22,80,25端口.
Chain INPUT (policy ACCEPT)
target prot opt source destination
target prot opt source destination
target prot opt source destination
[root@tp ~]# iptables -X 清除预设表filter中使用者自定链中的规则
Chain INPUT (policy ACCEPT)
target prot opt source destination
target prot opt source destination
target prot opt source destination
上面的意思是,当超出了IPTABLES里filter表里的两个链规则(INPUT,FORWARD)时,不在这两个规则里的数据包怎么处理呢,那就是DROP(放弃).应该说这样配置是很安全的.我们要控制流入数据包
如果做了邮件服务器,开启25,110端口.
[root@tp ~]# iptables -A INPUT -p tcp --dport 25 -j ACCEPT
如果做了FTP服务器,开启21端口
IPTABLES -A OUTPUT -o lo -p all -j ACCEPT(如果是OUTPUT DROP)
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
target prot opt source destination
SNAT all -- 192.168.0.0/24 anywhere to:211.101.46.235
target prot opt source destination
[root@tp sysconfig]# iptables -t nat -A PREROUTING -i eth0 -s 172.16.0.0/12 -j DROP
[root@tp sysconfig]# iptables -t nat -A PREROUTING -i eth0 -s 192.168.0.0/16 -j DROP
如果我们想,比如阻止MSN,QQ,BT等的话,需要找到它们所用的端口或者IP,(个人认为没有太大必要)
[root@tp ~]# iptables -A INPUT -m state --state INVALID -j DROP
[root@tp ~]# iptables -A OUTPUT -m state --state INVALID -j DROP
[root@tp ~]# iptables-A FORWARD -m state --state INVALID -j DROP
允许所有已经建立的和相关的连接
[root@tp ~]# iptables-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
[root@tp ~]# iptables-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
[root@tp ~]# /etc/rc.d/init.d/iptables save
这样就可以写到/etc/sysconfig/iptables文件里了.写入后记得把防火墙重起一下,才能起作用.
[root@tp ~]# service iptables restart
lucene修改相似度实现:去掉文本长度和重复词的影响 - sling2007的日志 - 网易博客
文档的分值代表了该文档在特定查询词下对应的相关性高低,他关联着信息检索向量空间模型中的向量夹角的接近度。一个文档越与查询词相关,得分越高。分值计算公式如下:
score(q,d) = coord(q,d) · queryNorm(q) · ∑ ( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) )
t in q
其中
tf(t in d)
这个值衡量着Term在文档中出现的频率,也就是词频。关键词在文档中出现的次数越多,得分越高,这个值在DefaultSimilarity的计算公式如下(词频的平方根):
tf(t in d) = frequency½
idf(t)
代表着该词的逆词频,这个值衡量了该词在整个文档库中出现的频度。这意味着,一个词出现的越少,根据香农的信息公示,他越珍稀。同时将贡献更多的分值给总 分值。默认的计算公式如下(其中numDocs代表整个文档的数量,docFreq代表了含有Term t的文档数量):
numDocs
idf(t) = 1 + log ( ––––––––– )
docFreq+1
coord(q,d)
一次查询可能查询好多条件,产生多个子查询。文档命中的子查询数越多,说明越符合查询,得分应该越高。
比如title:中国 || keyword:中国 || author:作者,这个查询有三个子查询,命中一个条件即为0.333,两个就是0.6667,都命中则为1
coord(q,d) = overlap / maxOverlap
queryNorm(q)
这个标准化因子用于在多个查询器中进行比较。它并不影响文档的排名。它的主要作用在于多个查询器返回的结果进行比较,甚至是结果来自多个索引时。这是搜索时的权重因子,当给查询器设置权重时就是通过这个因子进行影响的。默认的实现公式如下:
1
queryNorm(q) = queryNorm(sumOfSquaredWeights) = ––––––––––––––
sumOfSquaredWeights½
其中的sumOfSquaredWeights的计算公式如下:(可以清晰的看到获取query的boost,当没给查询器设置值时,默认为1,不起作用)
sumOfSquaredWeights = q.getBoost() 2 · ∑ ( idf(t) · t.getBoost() ) 2
t in q
t.getBoost()
该值是一个搜索时权重因子,可以在查询时给不同的Term设置不同的权重,可以通过lucene语法(具体参见我翻译的另外一篇文章:hi.baidu.com/expertsearch/blog/item/8d4f7d355a2e413c5ab5f547.html),也可以通过setBost()函数,注意,在多Term查询器中,是没有获取单一Term权重的函数的,所以如果需要获取,只能调用相应的子查询器函数的getBoost()函数。
norm(t,d)
封装了一些索引时因子以及长度因子。
Document boost - 在索引时,添加到Index前可以通过doc.setBoost()设置,衡量了Document的重要程度。.
Field boost - 在将字段加入到文档前可以通过调用field.setBoost()来设置字段的权重。
lengthNorm(field) - 该值在将文档添加到索引时,根据所有文档中特定字段的Term数来计算。所以默认更短的字段将贡献更多的分值。
1
lengthNorm(field) = ––––––––––––––
numTerms½
当文档加入索引时,以上因子将相乘,如果一个文档中有多个同名的字段,那么将多个多同的权重也相乘。
norm(t,d) = doc.getBoost() · lengthNorm(field) · ∏ f.getBoost()
field f in d named as t
可 是还有件值得注意的事情,这个值在索引时计算完毕后将编码为一个Byte存储起来,在搜索时,再从文件中读取出该值并解码成float。在这个过程中,可 能会造成精度的缺失,并不能保证decode(encode(x)) = x,比如,有可能decode(encode(0.89)) = 0.75,同样值得注意的是,在搜索时改变此值已经太晚了。例如,用一个不同于DefaultSimilarity的实现。
有关Lucene的问题(4):影响Lucene对文档打分的四种方式 - forfuture1978的专栏 - 博客频道 - CSDN.NET
在索引阶段设置Document Boost和Field Boost,存储在(.nrm)文件中。
如果希望某些文档和某些域比其他的域更重要,如果此文档和此域包含所要查询的词则应该得分较高,则可以在索引阶段设定文档的boost和域的boost值。
这些值是在索引阶段就写入索引文件的,存储在标准化因子(.nrm)文件中,一旦设定,除非删除此文档,否则无法改变。
如果不进行设定,则Document Boost和Field Boost默认为1。
Document Boost及FieldBoost的设定方式如下:
|
Document doc = new Document(); Field f = new Field("contents", "hello world", Field.Store.NO, Field.Index.ANALYZED); f.setBoost(100); doc.add(f); doc.setBoost(100); |
两者是如何影响Lucene的文档打分的呢?
让我们首先来看一下Lucene的文档打分的公式:
|
score(q,d) = coord(q,d) · queryNorm(q) · ∑( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) ) t in q |
Document Boost和Field Boost影响的是norm(t, d),其公式如下:
|
norm(t,d) = field f in d named as t |
它包括三个参数:
- Document boost:此值越大,说明此文档越重要。
- Field boost:此域越大,说明此域越重要。
- lengthNorm(field) = (1.0 / Math.sqrt(numTerms)):一个域中包含的Term总数越多,也即文档越长,此值越小,文档越短,此值越大。
其中第三个参数可以在自己的Similarity中影响打分,下面会论述。
当然,也可以在添加Field的时候,设置Field.Index.ANALYZED_NO_NORMS或Field.Index.NOT_ANALYZED_NO_NORMS,完全不用norm,来节约空间。
根据Lucene的注释,No norms means that index-time field and document boosting and field length normalization are disabled. The benefit is less memory usage as norms take up one byte of RAM per indexed field for every document in the index, during searching. Note that once you index a given field with norms enabled, disabling norms will have no effect. 没有norms意味着索引阶段禁用了文档boost和域的boost及长度标准化。好处在于节省内存,不用在搜索阶段为索引中的每篇文档的每个域都占用一个字节来保存norms信息了。但是对norms信息的禁用是必须全部域都禁用的,一旦有一个域不禁用,则其他禁用的域也会存放默认的norms值。因为为了加快norms的搜索速度,Lucene是根据文档号乘以每篇文档的norms信息所占用的大小来计算偏移量的,中间少一篇文档,偏移量将无法计算。也即norms信息要么都保存,要么都不保存。
下面几个试验可以验证norms信息的作用:
试验一:Document Boost的作用
|
public void testNormsDocBoost() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
如果第一篇文档的域f1也为Field.Index.ANALYZED_NO_NORMS的时候,搜索排名如下:
|
docid : 2 score : 1.2337708 |
如果第一篇文档的域f1设为Field.Index.ANALYZED,则搜索排名如下:
|
docid : 0 score : 39.889805 |
试验二:Field Boost的作用
如果我们觉得title要比contents要重要,可以做一下设定。
|
public void testNormsFieldBoost() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
如果第一篇文档的域f1也为Field.Index.ANALYZED_NO_NORMS的时候,搜索排名如下:
|
docid : 1 score : 0.49999997 |
如果第一篇文档的域f1设为Field.Index.ANALYZED,则搜索排名如下:
|
docid : 0 score : 19.79899 |
试验三:norms中文档长度对打分的影响
|
public void testNormsLength() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
当norms被禁用的时候,包含两个common的第二篇文档打分较高:
|
docid : 1 score : 0.13928263 |
当norms起作用的时候,虽然包含两个common的第二篇文档,由于长度较长,因而打分较低:
|
docid : 0 score : 0.09848769 |
试验四:norms信息要么都保存,要么都不保存的特性
|
public void testOmitNorms() throws Exception { |
当我们添加10001篇文档,所有的文档都设为Field.Index.ANALYZED_NO_NORMS的时候,我们看索引文件,发现.nrm文件只有1K,也即其中除了保持一定的格式信息,并无其他数据。
![[图]全部OmitNorms](http://hi.csdn.net/attachment/201002/8/3634917_1265644221YdM2.jpg)
当我们把第一篇文档设为Field.Index.ANALYZED,而其他10000篇文档都设为Field.Index.ANALYZED_NO_NORMS的时候,发现.nrm文件又10K,也即所有的文档都存储了norms信息,而非只有第一篇文档。
![[图]部分OmitNorms](http://hi.csdn.net/attachment/201002/8/3634917_1265644222UtXT.jpg)
在搜索语句中,设置Query Boost.
在搜索中,我们可以指定,某些词对我们来说更重要,我们可以设置这个词的boost:
common^4 hello |
使得包含common的文档比包含hello的文档获得更高的分数。
由于在Lucene中,一个Term定义为Field:Term,则也可以影响不同域的打分:
|
title:common^4 content:common |
使得title中包含common的文档比content中包含common的文档获得更高的分数。
实例:
|
public void testQueryBoost() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
根据tf/idf,包含两个common2的第二篇文档打分较高:
|
docid : 1 score : 0.24999999 |
如果我们输入的查询语句为:"common1^100 common2",则第一篇文档打分较高:
|
docid : 0 score : 0.2499875 |
那Query Boost是如何影响文档打分的呢?
根据Lucene的打分计算公式:
|
score(q,d) = coord(q,d) · queryNorm(q) · ∑( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) ) t in q |
注:在queryNorm的部分,也有q.getBoost()的部分,但是对query向量的归一化(见向量空间模型与Lucene的打分机制[http://forfuture1978.javaeye.com/blog/588721])。
继承并实现自己的Similarity
Similariy是计算Lucene打分的最主要的类,实现其中的很多借口可以干预打分的过程。
(1) float computeNorm(String field, FieldInvertState state)
(2) float lengthNorm(String fieldName, int numTokens)
(3) float queryNorm(float sumOfSquaredWeights)
(4) float tf(float freq)
(5) float idf(int docFreq, int numDocs)
(6) float coord(int overlap, int maxOverlap)
(7) float scorePayload(int docId, String fieldName, int start, int end, byte [] payload, int offset, int length)
它们分别影响Lucene打分计算的如下部分:
|
score(q,d) = (6)coord(q,d) · (3)queryNorm(q) · ∑( (4)tf(t in d) · (5)idf(t)2 · t.getBoost() · (1)norm(t,d) ) t in q |
|
norm(t,d) = field f in d named as t |
下面逐个进行解释:
(1) float computeNorm(String field, FieldInvertState state)
影响标准化因子的计算,如上述,他主要包含了三部分:文档boost,域boost,以及文档长度归一化。此函数一般按照上面norm(t, d)的公式进行计算。
(2) float lengthNorm(String fieldName, int numTokens)
主要计算文档长度的归一化,默认是1.0 / Math.sqrt(numTerms)。
因为在索引中,不同的文档长度不一样,很显然,对于任意一个term,在长的文档中的tf要大的多,因而分数也越高,这样对小的文档不公平,举一个极端的例子,在一篇1000万个词的鸿篇巨著中,"lucene"这个词出现了11次,而在一篇12个词的短小文档中,"lucene"这个词出现了10次,如果不考虑长度在内,当然鸿篇巨著应该分数更高,然而显然这篇小文档才是真正关注"lucene"的。
因而在此处是要除以文档的长度,从而减少因文档长度带来的打分不公。
然而现在这个公式是偏向于首先返回短小的文档的,这样在实际应用中使得搜索结果也很难看。
于是在实践中,要根据项目的需要,根据搜索的领域,改写lengthNorm的计算公式。比如我想做一个经济学论文的搜索系统,经过一定时间的调研,发现大多数的经济学论文的长度在8000到10000词,因而lengthNorm的公式应该是一个倒抛物线型的,8000到10000词的论文分数最高,更短或更长的分数都应该偏低,方能够返回给用户最好的数据。
(3) float queryNorm(float sumOfSquaredWeights)
这是按照向量空间模型,对query向量的归一化。此值并不影响排序,而仅仅使得不同的query之间的分数可以比较。
(4) float tf(float freq)
freq是指在一篇文档中包含的某个词的数目。tf是根据此数目给出的分数,默认为Math.sqrt(freq)。也即此项并不是随着包含的数目的增多而线性增加的。
(5) float idf(int docFreq, int numDocs)
idf是根据包含某个词的文档数以及总文档数计算出的分数,默认为(Math.log(numDocs/(double)(docFreq+1)) + 1.0)。
由于此项计算涉及到总文档数和包含此词的文档数,因而需要全局的文档数信息,这给跨索引搜索造成麻烦。
从下面的例子我们可以看出,用MultiSearcher来一起搜索两个索引和分别用IndexSearcher来搜索两个索引所得出的分数是有很大差异的。
究其原因是MultiSearcher的docFreq(Term term)函数计算了包含两个索引中包含此词的总文档数,而IndexSearcher仅仅计算了每个索引中包含此词的文档数。当两个索引包含的文档总数是有很大不同的时候,分数是无法比较的。
|
public void testMultiIndex() throws Exception{ |
|
结果为: ------------------------------- |
如果几个索引都是在一台机器上,则用MultiSearcher或者MultiReader就解决问题了,然而有时候索引是分布在多台机器上的,虽然Lucene也提供了RMI,或用NFS保存索引的方法,然而效率和并行性一直是一个问题。
一个可以尝试的办法是在Similarity中,idf返回1,然后多个机器上的索引并行搜索,在汇总结果的机器上,再融入idf的计算。
如下面的例子可以看出,当idf返回1的时候,打分可以比较了:
|
class MultiIndexSimilarity extends Similarity { @Override |
|
----------------------------- |
(6) float coord(int overlap, int maxOverlap)
一次搜索可能包含多个搜索词,而一篇文档中也可能包含多个搜索词,此项表示,当一篇文档中包含的搜索词越多,则此文档则打分越高。
|
public void TestCoord() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
|
class MySimilarity extends Similarity { @Override } |
如上面的实例,当coord返回1,不起作用的时候,文档一虽然包含了两个搜索词common和world,但由于world的所在的文档数太多,而文档二包含common的次数比较多,因而文档二分数较高:
|
docid : 1 score : 1.9059997 |
而当coord起作用的时候,文档一由于包含了两个搜索词而分数较高:
|
class MySimilarity extends Similarity { @Override } |
|
docid : 0 score : 1.2936771 |
(7) float scorePayload(int docId, String fieldName, int start, int end, byte [] payload, int offset, int length)
由于Lucene引入了payload,因而可以存储一些自己的信息,用户可以根据自己存储的信息,来影响Lucene的打分。
payload的定义
我们知道,索引是以倒排表形式存储的,对于每一个词,都保存了包含这个词的一个链表,当然为了加快查询速度,此链表多用跳跃表进行存储。
Payload信息就是存储在倒排表中的,同文档号一起存放,多用于存储与每篇文档相关的一些信息。当然这部分信息也可以存储域里(stored Field),两者从功能上基本是一样的,然而当要存储的信息很多的时候,存放在倒排表里,利用跳跃表,有利于大大提高搜索速度。
Payload的存储方式如下图:
由payload的定义,我们可以看出,payload可以存储一些不但与文档相关,而且与查询词也相关的信息。比如某篇文档的某个词有特殊性,则可以在这个词的这个文档的position信息后存储payload信息,使得当搜索这个词的时候,这篇文档获得较高的分数。
要利用payload来影响查询需要做到以下几点,下面举例用标记的词在payload中存储1,否则存储0:
首先要实现自己的Analyzer从而在Token中放入payload信息:
|
class BoldAnalyzer extends Analyzer { @Override } class BoldFilter extends TokenFilter { private TermAttribute termAtt; protected BoldFilter(TokenStream input) { @Override final char[] buffer = termAtt.termBuffer(); String tokenstring = new String(buffer, 0, length); public static int bytes2int(byte[] b) { public static byte[] int2bytes(int num) { } |
然后,实现自己的Similarity,从payload中读出信息,根据信息来打分。
|
class PayloadSimilarity extends DefaultSimilarity { @Override |
最后,查询的时候,一定要用PayloadXXXQuery(在此用PayloadTermQuery,在Lucene 2.4.1中,用BoostingTermQuery),否则scorePayload不起作用。
|
public void testPayloadScore() throws Exception { IndexReader reader = IndexReader.open(FSDirectory.open(indexDir)); |
如果scorePayload函数始终是返回1,则结果如下,不起作用。
|
It is not a bold char. |
如果scorePayload函数如下:
|
class PayloadSimilarity extends DefaultSimilarity { @Override |
则结果如下,同样是包含hello,包含加粗的文档获得较高分:
|
It is not a bold char. |
继承并实现自己的collector
以上各种方法,已经把Lucene score计算公式的所有变量都涉及了,如果这还不能满足您的要求,还可以继承实现自己的collector。
在Lucene 2.4中,HitCollector有个函数public abstract void collect(int doc, float score),用来收集搜索的结果。
其中TopDocCollector的实现如下:
|
public void collect(int doc, float score) { |
此函数将docid和score插入一个PriorityQueue中,使得得分最高的文档先返回。
我们可以继承HitCollector,并在此函数中对score进行修改,然后再插入PriorityQueue,或者插入自己的数据结构。
比如我们在另外的地方存储docid和文档创建时间的对应,我们希望当文档时间是一天之内的分数最高,一周之内的分数其次,一个月之外的分数很低。
我们可以这样修改:
|
public static long milisecondsOneDay = 24L * 3600L * 1000L; public static long millisecondsOneWeek = 7L * 24L * 3600L * 1000L; public static long millisecondsOneMonth = 30L * 24L * 3600L * 1000L; public void collect(int doc, float score) { long time = getTimeByDocId(doc); if(time < milisecondsOneDay) { score = score * 1.0; } else if (time < millisecondsOneWeek){ score = score * 0.8; } else if (time < millisecondsOneMonth) { score = score * 0.3; } else { score = score * 0.1; } totalHits++; |
在Lucene 3.0中,Collector接口为void collect(int doc),TopScoreDocCollector实现如下:
|
public void collect(int doc) throws IOException { |
同样可以用上面的方式影响其打分。
lucene之排序、设置权重、优化、分布式搜索_孤独浪子_新浪博客
2. 多字段搜索
使用 MultiFieldQueryParser 可以指定多个搜索字段。
IndexReader reader = IndexReader.Open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
3. 多条件搜索
除了使用 QueryParser.Parse 分解复杂的搜索语法外,还可以通过组合多个 Query 来达到目的。
Query query2 = new WildcardQuery(new Term(FieldName, "name*")); // 通配符
//Query query3 = new PrefixQuery(new Term(FieldName, "name1")); // 字段搜索 Field:Keyword,自动在结尾添加 *
//Query query4 = new RangeQuery(new Term(FieldNumber, NumberTools.LongToString(11L)), new Term(FieldNumber, NumberTools.LongToString(13L)), true); // 范围搜索
//Query query5 = new FilteredQuery(query, filter); // 带过滤条件的搜索
BooleanQuery query = new BooleanQuery();
query.Add(query1, BooleanClause.Occur.MUST);
query.Add(query2, BooleanClause.Occur.MUST);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
4. 设置权重
可以给 Document 和 Field 增加权重(Boost),使其在搜索结果排名更加靠前。缺省情况下,搜索结果以 Document.Score 作为排序依据,该数值越大排名越靠前。Boost 缺省值为 1。
通过上面的公式,我们就可以设置不同的权重来影响排名。
如下面的例子中根据 VIP 级别设定不同的权重。
switch (vip)
{
}
只要 Boost 足够大,那么就可以让某个命中结果永远排第一位,这就是百度等网站的"收费排名"业务。明显有失公平,鄙视一把。 ![]()
5. 排序
通过 SortField 的构造参数,我们可以设置排序字段,排序条件,以及倒排。
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query, sort);
排序对搜索速度影响还是很大的,尽可能不要使用多个排序条件。
6. 过滤
使用 Filter 对搜索结果进行过滤,可以获得更小范围内更精确的结果。
举个例子,我们搜索上架时间在 2005-10-1 到 2005-10-30 之间的商品。
对于日期时间,我们需要转换一下才能添加到索引库,同时还必须是索引字段。
document.Add(FieldDate, DateField.DateToString(date), Field.Store.YES, Field.Index.UN_TOKENIZED);
//...
// search
Filter filter = new DateFilter(FieldDate, DateTime.Parse("2005-10-1"), DateTime.Parse("2005-10-30"));
Hits hits = searcher.Search(query, filter);
除了日期时间,还可以使用整数。比如搜索价格在 100 ~ 200 之间的商品。
Lucene.Net NumberTools 对于数字进行了补位处理,如果需要使用浮点数可以自己参考源码进行。
document.Add(new Field(FieldNumber, NumberTools.LongToString((long)price), Field.Store.YES, Field.Index.UN_TOKENIZED));
//...
// search
Filter filter = new RangeFilter(FieldNumber, NumberTools.LongToString(100L), NumberTools.LongToString(200L), true, true);
Hits hits = searcher.Search(query, filter);
使用 Query 作为过滤条件。
我们还可以使用 FilteredQuery 进行多条件过滤。
Filter filter2 = new RangeFilter(FieldNumber, NumberTools.LongToString(11L), NumberTools.LongToString(13L), true, true);
Query query = QueryParser.Parse("name*", FieldName, analyzer);
query = new FilteredQuery(query, filter);
query = new FilteredQuery(query, filter2);
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
7. 分布搜索
我们可以使用 MultiReader 或 MultiSearcher 搜索多个索引库。
IndexSearcher searcher = new IndexSearcher(reader);
Hits hits = searcher.Search(query);
或
IndexSearcher searcher2 = new IndexSearcher(reader2);
MultiSearcher searcher = new MultiSearcher(new Searchable[] { searcher1, searcher2 });
Hits hits = searcher.Search(query);
还可以使用 ParallelMultiSearcher 进行多线程并行搜索。
8. 合并索引库
将 directory1 合并到 directory2 中。
Directory directory2 = FSDirectory.GetDirectory("index2", false);
IndexWriter writer = new IndexWriter(directory2, analyzer, false);
writer.AddIndexes(new Directory[] { directory });
Console.WriteLine(writer.DocCount());
writer.Close();
9. 显示搜索语法字符串
我们组合了很多种搜索条件,或许想看看与其对等的搜索语法串是什么样的。
query.Add(query1, true, false);
query.Add(query2, true, false);
//...
Console.WriteLine("Syntax: {0}", query.ToString());
输出:
Syntax: +(name:name* value:name*) +number:[0000000000000000b TO 0000000000000000d]
呵呵,就这么简单。
10. 操作索引库
删除 (软删除,仅添加了删除标记。调用 IndexWriter.Optimize() 后真正删除。)
// 删除指定序号(DocId)的 Document。
reader.Delete(123);
// 删除包含指定 Term 的 Document。
reader.Delete(new Term(FieldValue, "Hello"));
// 恢复软删除。
reader.UndeleteAll();
reader.Close();
增量更新 (只需将 create 参数设为 false,即可往现有索引库添加新数据。)
IndexWriter writer = new IndexWriter(directory, analyzer, false);
writer.AddDocument(doc1);
writer.AddDocument(doc2);
writer.Optimize();
writer.Close();
11. 优化
批量向 FSDirectory 增加索引时,增大合并因子(mergeFactor )和最小文档合并数(minMergeDocs)有助于提高性能,减少索引时间。
writer.maxFieldLength = 1000; // 字段最大长度
writer.mergeFactor = 1000;
writer.minMergeDocs = 1000;
for (int i = 0; i < 10000; i++)
{
}
writer.Optimize();
writer.Close();
五、CXF WebService整合Spring - hoojo - 博客园
applicationContext-client.xml配置文件,文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans >
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd"
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
<jaxws:client id="userWsClient" serviceClass="com.hoo.service.IComplexUserService"
address="http://localhost:8080/CXFWebService/Users"/>
</beans>
BSTEK Development Framework (BDF) - 资料库
BDF开发框架文档
BDF的应用目标是基于J2EE的B/S项目,所以只要您是要做的项目是J2EE架构的B/S项目,同时又打算使用dorado7,那么就可以采用BDF发行包快速搭建一个包含BDF各个功能模块的dorado7项目。
要配置一个包含BDF的dorado7应用,那么添加BDF发行包下的com.bstek.bdf.core及com.bstek.bdf.core.dorado7两个模块是必须的。
BDF发行包中包含若干个目录,每个目录分别表示各个不同模块,比如com.bstek.bdf.security就表示BDF的安全模块,在这些模块当中,有的需要管理界面,BDF提供了基于Dorado7的管理页面,这些页面则放在以dorado7结尾的模块当中,比如安全模块的管理界面所在目录就是com.bstek.bdf.security.dorado7;在后续的叫法当中,我们将省略com.bstek.bdf前缀,比如对安全模块,我们就直接称之为security模块,对于安全模块的管理界面则称之为security.dorado7模块,依次类推。
Quartz与Spring整合进行热部署的实现 - pigwing - 博客园
先来几张实现图

任务管理页.

新建任务管理.目前实现叫简单的需求...若各位同学要实现复杂的设计...quartz都有提供强大的支持.小弟目前的需求做到这已经够用了.
接下来.我们如何实现quartz的热部署编码呢?
小弟是利用spring整合quzrtz实现的.但是会产生两个小问题.
我们先看看quartz如何与spring整合
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
<property name="configLocation" value="classpath:quartz.properties"/><!--
这个是必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动
-->
<property name="startupDelay" value="30"/><!--
这个是可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
-->
<property name="overwriteExistingJobs" value="true"/>
<property name="jobDetails" >
<list>
<ref bean="xxxJobDetail"/>
</list>
</property>
</bean>
首先.我们需要添加一个数据源给quzrtz.允许其序列化JobDetail到数据库.之后有注释.呵呵.我就不必多说了.
<bean id="xxxJobDetail" class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--shouldRecover属性为true,则当Quartz服务被中止后,再次启动任务时会尝试恢复执行之前未完成的所有任务-->
<!--<property name="shouldRecover" value="true"/>-->
<!-- 标识job是持久的,删除触发器的时候不被删除 -->
<property name="durable" value="true"/>
<property name="targetObject" ref="xxxxJob"/>
<!-- 此处是需要执行的任务的方法 -->
<property name="targetMethod" value="executeJob"/>
</bean>
凡是使用过quartz跟spring整合的同学会发现.为什么class的命名空间不是org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean呢?
因为spring技术小组的class会报NotSerializableException.且功能不强shouldRecover,durable这些基础属性不提供...大概spring的MethodInvokingJobDetailFactoryBean根本不支持JobDetail的序列化...想详细了解原因.可以看spring的源码.目前小弟使用的spring3,spring小组依然没解决这问题,应该说还不支持JobDetail序列化.但国外牛人们已经帮我们解决好了.详细见下连接
http://jira.springframework.org/browse/SPR-3797
好了.接下来我们需要配置quzrtz的properties(放到classpath下.quzrtz就能找到)
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.useProperties = true
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.jobStore.maxMisfiresToHandleAtATime=1
我们这次是选择org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
而不是默认的org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore简单存储在内存中.
接来下,我们需要一个quartz的scheduler管理类.
protected final Log log = LogFactory.getLog(getClass());
private Scheduler scheduler;
private QuartzDao quartzDao;
private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceImpl.class);
public void setScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
}
public void setQuartzDao(QuartzDao quartzDao) {
this.quartzDao = quartzDao;
}
/**
* 根据job的名称获取job,进而添加到trigger
* @param name
* @param jobName
* @param cronExpression
* @param group
* @throws SchedulerException
*/
public void schedule(String name, String jobName, String cronExpression,String group) throws SchedulerException {
if (name == null || name.trim().equals("")) {
name = UUID.randomUUID().toString();
}
try {
JobDetail jobDetail = scheduler.getJobDetail(jobName, group);
if(jobDetail != null) {
scheduler.addJob(jobDetail, true);
CronTrigger cronTrigger = new CronTrigger(name, group, jobDetail.getName(),
Scheduler.DEFAULT_GROUP);
cronTrigger.setCronExpression(new CronExpression(cronExpression));
scheduler.scheduleJob(cronTrigger);
scheduler.rescheduleJob(cronTrigger.getName(), cronTrigger.getGroup(), cronTrigger);
}else
log.error("无法找到对应的job.所以无法建立trigger");
} catch (SchedulerException e) {
log.error(e.getMessage());
throw new SchedulerException();
} catch (ParseException e) {
log.error(e.getMessage());
}
}
public List<Map<String, Object>> getQrtzTriggers(){
return quartzDao.getQrtzTriggers();
}
public void pauseTrigger(String triggerName,String group) throws SchedulerException{
try {
scheduler.pauseTrigger(triggerName, group);//停止触发器
} catch (SchedulerException e) {
log.error(e.getMessage());
throw new SchedulerException();
}
}
public void resumeTrigger(String triggerName,String group) throws SchedulerException{
try {
scheduler.resumeTrigger(triggerName, group);//重启触发器
} catch (SchedulerException e) {
log.error(e.getMessage());
throw new SchedulerException();
}
}
public boolean removeTrigdger(String triggerName,String group) throws SchedulerException{
try {
scheduler.pauseTrigger(triggerName, group);//停止触发器
return scheduler.unscheduleJob(triggerName, group);//移除触发器
} catch (SchedulerException e) {
log.error(e.getMessage());
throw new SchedulerException();
}
}
public String[] getJobNames(String group) throws SchedulerException {
String[] jobNames = null;
try {
jobNames = scheduler.getJobNames(group);
} catch (SchedulerException e) {
log.error(e.getMessage());
throw new SchedulerException();
}
return jobNames;
}
但前只是简单实现通过cornexpression.若有复杂配置trigger规则的.或需要对trigger添加calendar...可以自己进行扩展.
一下是quzrtzDao的实现
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<Map<String, Object>> getQrtzTriggers() {
List<Map<String, Object>> results = getJdbcTemplate().queryForList("select * from QRTZ_TRIGGERS order by start_time");
long val = 0;
String temp = null;
for (Map<String, Object> map : results) {
temp = MapUtils.getString(map, "trigger_name");
if(StringUtils.indexOf(temp, "#") != -1){
map.put("display_name", StringUtils.substringBefore(temp, "#"));
}else{
map.put("display_name", temp);
}
val = MapUtils.getLongValue(map, "next_fire_time");
if (val > 0) {
map.put("next_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
}
val = MapUtils.getLongValue(map, "prev_fire_time");
if (val > 0) {
map.put("prev_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
}
val = MapUtils.getLongValue(map, "start_time");
if (val > 0) {
map.put("start_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
}
val = MapUtils.getLongValue(map, "end_time");
if (val > 0) {
map.put("end_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
}
map.put("statu",status.get(MapUtils.getString(map, "trigger_state")));
}
return results;
}
public static final Map<String,String> status = new HashMap<String,String>();
static{
status.put("ACQUIRED", "运行");
status.put("PAUSED", "暂停");
status.put("WAITING", "等待");
}
private JdbcTemplate getJdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
同学们可以根据自己的风格,修改一下其代码.
此时我们就可以创建一个简单的quzrtz的热部署管理.
我之前讲过会有两个问题.第二个问题是什么呢?还记得讲过我们需要讲jobDetail序列化到数据库吗?因为我们使用了spring 系统中的manager类通过spring的IOC依赖注入.那我们的跟quartz相关的manager都需要声明实现Serializable序列化接口.
此时我们可以想到用ContextLoader.getCurrentWebApplicationContext()把相应的manage通过bean id get出来.
public class SpringBeanProvide implements Serializable/*, ApplicationContextAware*/{
/**
*
*/
private static final long serialVersionUID = 8430477279431126488L;
private ApplicationContext context;
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> clazz, String beanName){
context = ContextLoader.getCurrentWebApplicationContext();
return (T)context.getBean(beanName);
}
public ServletContext getServletContext() {
WebApplicationContext webContext = ContextLoader.getCurrentWebApplicationContext();
return webContext.getServletContext();
}
/*@Autowired
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}*/
}
我们看看ContextLoader.getCurrentWebApplicationContext()的源码
/**
* Obtain the Spring root web application context for the current thread
* (i.e. for the current thread's context ClassLoader, which needs to be
* the web application's ClassLoader).
* @return the current root web application context, or <code>null</code>
* if none found
* @see org.springframework.web.context.support.SpringBeanAutowiringSupport
*/
public static WebApplicationContext getCurrentWebApplicationContext() {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl != null) {
WebApplicationContext ccpt = currentContextPerThread.get(ccl);
if (ccpt != null) {
return ccpt;
}
}
return currentContext;
}
currentContextPerThread是一个final的ConcurrentHashMap.也是IOC容器存储bean的一个hash表.
到底什么时候才把ccl设置到currentContextPerThread中呢?
我们往上看源码
try {
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
其中...currentContextPerThread.put(ccl, this.context);是在ApplicationContext parent = loadParentContext(servletContext);之下.
ContextLoader.getCurrentWebApplicationContext()只能在容器完全初始化后才能使用..这对于单元测试时...ccpt返回出来的是一个null...
这就是我为什么会在SpringBeanProvide出现注释了ApplicationContextAware的原因...因为本人有点懒...哈哈..没重新整理一份spring的bean xml作为单元测试使用...所以...在单元测试时...改一下注释...就去跑了..呵呵...此时...我们已经可以使用quzrtz的热部署.不必重启服务器就可以对任务进行管理了.

