关于Google的Suggest功能的实现
@@@使用Ajax及Lucene对其进行完美实现@@@ (飞刀和雨)
大家都对Google的suggest的功能大概很有兴趣吧,我们觉得既然Google做的出,那么我们也能做出来,先对其进行分析,1.对于网页客户端的按键的动态变化,Ajax是最好的选择,
2.如果数据库的选择,则可以有多种选择得,BerkeleyDB, Derby甚至自己做个txt文本文件,把所需要的单词和result数目放在文本文件里都能够实现,但今天我们有了一个很好的索引工具Lucene。加上Lucene对我的吸引力,因此今天我们用上大炮,卸去鸟枪。开始我们的开发之路。
首先,我们先建立个demo的框架,我就做了个这样的一个html文件,用了一个form,一个
输入的文本框,一个多选项和一个确认按键。后面两个没什么好说的,主要是文本框的设计,我做了以下的定义,<input type="text" size=60 id="userid" name="q" autocomplete="off" onKeyUp="validate(event);"> 这样autocomplete=off指取消网页的cache,这样就不会弹出以前打过的字的窗口,造成混乱。onKeyUp是实现ajax的关键,相应键盘输入的操作。
其次,开始我们的Ajax了,顾名思义,Ajax指异步的javascript和xml. 我们的所有工作都会在javascript中完成,至于ajax原理,很多地方都有很详细的解释,这里就指列应用了。
validate(e){
var key=e.KeyCode; //获得输入的键值
//定义按键只对字母数字,空格,回车,Ins和del有效,可以避免一些无效的相应,并//转发url给servlet,那么我们只要等servlet返回xml就可以了
if (key>=48 && key<=90 || key==8 || key==32 || key== 45 || key==46){
var url = "LuceneSearch?id=" + encodeURI(idField.value);
req.open("GET", url, true);
req.onreadystatechange = processRequest;
req.send(null);
}
}
这时,可以通过Ajax的3个req的请求进行向服务器发送,我们这里只要等待服务器返回的xml就可以了。
function processRequest(){
if(req.readyState==4){
if(req.status==200){
parseMessages();
}else{
clearTable();
}
}
}
这里我们通过返回的状态, 得到一个XmlHttp的readyState=4表示servlet的操作结束,status
=200则表示Http得到正常的返回,这时调用parseMessage()就可以对所得到的XmlHttp进行操作,
function parseMessages(){
var products = req.responseXML.getElementsByTagName("products")[0];
for (loop=0;loop<products.childNodes.length);loop++){
var product = products.childNodes[loop];
var productname = product.getElementsByTagName("pname")[0];
var productnumber = product.getElementsByTagName("pnumber")[0];
}
}
这里我们就在javascipt里就得到了我们所需要的词的name和number,然后就只要在javascript里填入一些特效,便能很方便的实现Google的suggest.
其次,我们所需要的就事编写我们现在的LuceneSearch的servlet. 这里因为通过get方式传递,这不对于开发j2ee的程序员来说,就是小菜一碟,我们只要定义doGet(req, res)就能可以了实现了。从这里我们得到了Text文本框里的字符串值。
public class LuceneSearch extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String targetId = request.getParameter("id");
}
}
然后我们需要对这些字符串进行清理,去掉些无效的符号,然后把多余空格合并,成为一种真正的需要的字符串,然后我们需要用Lucene来进行搜索,先把Document把数据从数据库里读出来,然后制成Index。然后用Search来进行搜索,这里Lucene提供了很好的搜索方式,搜索分两种方式,一种是单个单词,这个就比较好办,Lucene 提供了开头匹配的方法PrefixQuery(), 直接套用就可以了
PrefixQuery query = new PrefixQuery(new Term("keyword", targetId));
IndexSearcher searcher = new IndexSearcher(directory);
Hits hits = searcher.search(query,sort);
for(int i=0; i<hits.length();i++){
sb.append("<product>");
sb.append("<pname>"+hits.doc(i).get("keyword")+"</pname>");
sb.append("<pnumber>"+hits.doc(i).get("number")+"</pnumber>");
sb.append("</product>");
}
这样我们就把搜索到的词和数量都放进Xml里了这样就出来了。
我们还可以注意到如果所需要的词并不在开头也能得到实现。
对于词组就比较费尽,因为首先要保证前面词的位置性,可以任意放置,最后一个词则必须以开头为基准进行模糊查找,这样才能实现其功能,还好Lucene还是考虑到了这点,有个PhrasePrefixQuery()的方法,稍稍加以改进便可以实现,
//首先要把词组用split以空格分开
String[] targetIdArray = targetId.split(" ");
PhrasePrefixQuery query = new PhrasePrefixQuery();
for(int i=0; i<targetIdArray.length-1;i++){
query.add(new Term("keyword",targetIdArray[i]));
}
query.setSlop(4); //设置词前后位置移动范围。
LinkedList termsWithPrefix = new LinkedList();
IndexReader ir = IndexReader.open(directory);
TermEnum te = ir.terms(new Term("keyword", targetIdArray[targetIdArray.length-1]));
do {
if (te.term().text().startsWith(targetIdArray[targetIdArray.length-1])) {
termsWithPrefix.add(te.term());
}
} while (te.next());
这样我们先得到最后一个单词为开头的词,然后加到PrasePrefixQuery里
query.add((Term[])termsWithPrefix.toArray(new Term[0]));
Hits hits;
hits = searcher.search(query, sort);
for(int i=0; i<(hits.length()>20?20:hits.length());i++){
sb.append("<product>");
sb.append("<pname>"+hits.doc(i).get("keyword")+"</pname>");
sb.append("<pnumber>"+hits.doc(i).get("popularity")+"</pnumber>");
sb.append("</product>");
}
这样我们就可以得到我们所需要的词组了。如图所示,很方便吧,这里就基本完成了google
所代表的suggest功能,当然,javascript里面还有一些上下键,鼠标移动事件的触发,加在一起就会把我们的网页弄得很完美了。但这些都不是重点,我们主要是对门户网站功能的实现,如果有问题和建议,可以给我留言,谢谢。