关于GoogleSuggest功能的实现

@@@使用AjaxLucene对其进行完美实现@@@ (飞刀和雨)

 

大家都对Googlesuggest的功能大概很有兴趣吧,我们觉得既然Google做的出,那么我们也能做出来,先对其进行分析,1.对于网页客户端的按键的动态变化,Ajax是最好的选择, google_suggest.JPG

2.如果数据库的选择,则可以有多种选择得,BerkeleyDB, Derby甚至自己做个txt文本文件,把所需要的单词和result数目放在文本文件里都能够实现,但今天我们有了一个很好的索引工具Lucene。加上Lucene对我的吸引力,因此今天我们用上大炮,卸去鸟枪。开始我们的开发之路。

 

首先,我们先建立个demo的框架,我就做了个这样的一个html文件,用了一个form,一个search_suggest.JPG 

输入的文本框,一个多选项和一个确认按键。后面两个没什么好说的,主要是文本框的设计,我做了以下的定义,<input  type="text" size=60 id="userid" name="q" autocomplete="off" onKeyUp="validate(event);"> 这样autocompleteoff指取消网页的cache,这样就不会弹出以前打过的字的窗口,造成混乱。onKeyUp是实现ajax的关键,相应键盘输入的操作。

 

其次,开始我们的Ajax了,顾名思义,Ajax指异步的javascriptxml. 我们的所有工作都会在javascript中完成,至于ajax原理,很多地方都有很详细的解释,这里就指列应用了。

 validate(e){

        var key=e.KeyCode;  //获得输入的键值

  //定义按键只对字母数字,空格,回车,Insdel有效,可以避免一些无效的相应,并//转发urlservlet,那么我们只要等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);

    }

}

这时,可以通过Ajax3req的请求进行向服务器发送,我们这里只要等待服务器返回的xml就可以了。

       function processRequest(){

             if(req.readyState==4){

                    if(req.status==200){

                           parseMessages();

                  }else{

                           clearTable();

                     }

              }

}

这里我们通过返回的状态, 得到一个XmlHttpreadyState=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里就得到了我们所需要的词的namenumber,然后就只要在javascript里填入一些特效,便能很方便的实现Googlesuggest.

 

其次,我们所需要的就事编写我们现在的LuceneSearchservlet. 这里因为通过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里了这样就出来了。

search_demo1.JPG

我们还可以注意到如果所需要的词并不在开头也能得到实现。

 

对于词组就比较费尽,因为首先要保证前面词的位置性,可以任意放置,最后一个词则必须以开头为基准进行模糊查找,这样才能实现其功能,还好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

search_demo2.JPG

所代表的suggest功能,当然,javascript里面还有一些上下键,鼠标移动事件的触发,加在一起就会把我们的网页弄得很完美了。但这些都不是重点,我们主要是对门户网站功能的实现,如果有问题和建议,可以给我留言,谢谢。