Lucene是apache軟件基金會 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包及架構,提供了完整的查詢引擎和索引引擎,實現了一些通用的分詞算法,預留很多詞法分析器接口。本文以
myrss.easyjf.com網站系統中使用Lucene實現全文檢索的代碼為例,簡單演示Lucene在實際項目中的應用。
使用Lucene實現全文檢索,主要有下面三個步驟:
1、建立索引庫:根據網站新聞信息庫中的已有的數據資料建立Lucene索引文件。
2、通過索引庫搜索:有了索引后,即可使用標準的詞法分析器或直接的詞法分析器實現進行全文檢索。
3、維護索引庫:網站新聞信息庫中的信息會不斷的變動,包括新增、修改及刪除等,這些信息的變動都需要進一步反映到Lucene索引文件中。
??? 下面是myrss.easyjf.com相關代碼!
?
?一、索引管理(建立及維護)
索引管理類MyRssIndexManage主要實現根據網站信息庫中的數據建立索引,維護索引等。由于索引的過程需要消耗一定的時間,因此,索引管理類實現Runnable接口,使得我們可以在程序中開新線程來運行。
package com.easyjf.lucene;
import java.util.Date;
import java.util.List;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import com.easyjf.dbo.EasyJDB;
import com.easyjf.news.business.NewsDir;
import com.easyjf.news.business.NewsDoc;
import com.easyjf.news.business.NewsUtil;
import com.easyjf.web.tools.IPageList;
public class MyRssIndexManage implements Runnable {
?private String indexDir;
?private String indexType="add";?
?public void run() {
??// TODO Auto-generated method stub
??if("add".equals(indexType))
???normalIndex();
??else if ("init".equals(indexType)) reIndexAll();
?}
?public void normalIndex()
?{
??try{
???Date start = new Date();
???int num=0;
???IndexWriter writer=new IndexWriter(indexDir,new StandardAnalyzer(),false);???
???//NewsDir dir=NewsDir.readBySn();???
???String scope="(needIndex<2) or(needIndex is null)";
???IPageList pList=NewsUtil.pageList(scope,1,50);
???for(int p=0;p<pList.getPages();p++)
???{
???pList=NewsUtil.pageList(scope,p,100);
???List list=pList.getResult();
???for(int i=0;i<list.size();i++)
???{
????NewsDoc doc=(NewsDoc)list.get(i);?????
????writer.addDocument(newsdoc2lucenedoc(doc));???
????num++;
???}
???}
???writer.optimize();
???writer.close();
???EasyJDB.getInstance().execute("update NewsDoc set needIndex=2 where "+scope);
???Date end = new Date();
???System.out.print("新增索引"+num+"條信息,一共花:"+(end.getTime() - start.getTime())/60000+"分鐘!");??
???}
???catch(Exception e)
???{
????e.printStackTrace();
???}
?}
?public void reIndexAll()
?{
??try{
???Date start = new Date();
???int num=0;
???IndexWriter writer=new IndexWriter(indexDir,new StandardAnalyzer(),true);???
???NewsDir dir=NewsDir.readBySn("easyjf");???
???IPageList pList=NewsUtil.pageList(dir,1,50);
???for(int p=0;p<pList.getPages();p++)
???{
???pList=NewsUtil.pageList(dir,p,100);
???List list=pList.getResult();
???for(int i=0;i<list.size();i++)
???{????
????NewsDoc doc=(NewsDoc)list.get(i);?????
????writer.addDocument(newsdoc2lucenedoc(doc));
????num++;
???}
???}
???writer.optimize();
???writer.close();
???EasyJDB.getInstance().execute("update NewsDoc set needIndex=2 where dirPath like 'easyjf%'");
???Date end = new Date();
???System.out.print("全部重新做了一次索引,一共處理了"+num+"條信息,花:"+(end.getTime() - start.getTime())/60000+"分鐘!");??
???}
???catch(Exception e)
???{
????e.printStackTrace();
???}
?}
?private Document newsdoc2lucenedoc(NewsDoc doc)
?{
??Document lDoc=new Document();?????
??lDoc.add(new Field("title",doc.getTitle(),Field.Store.YES,Field.Index.TOKENIZED));
??lDoc.add(new Field("content",doc.getContent(),Field.Store.YES,Field.Index.TOKENIZED));
??lDoc.add(new Field("url",doc.getRemark(),Field.Store.YES,Field.Index.NO));????
??lDoc.add(new Field("cid",doc.getCid(),Field.Store.YES,Field.Index.NO));
??lDoc.add(new Field("source",doc.getSource(),Field.Store.YES,Field.Index.NO));
??lDoc.add(new Field("inputTime",doc.getInputTime().toString(),Field.Store.YES,Field.Index.NO));
??return lDoc;
?}
?public String getIndexDir() {
??return indexDir;
?}
?public void setIndexDir(String indexDir) {
??this.indexDir = indexDir;
?}
?
?public String getIndexType() {
??return indexType;
?}
?public void setIndexType(String indexType) {
??this.indexType = indexType;
?}
}
?
二、使用Lucene實現全文搜索
?? 下面是MyRssSearch類的源碼,該類主要實現使用Lucene中Searcher及QueryParser實現從索引庫中搜索關鍵詞。
package com.easyjf.lucene;
import java.util.List;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import com.easyjf.search.MyRssUtil;
import com.easyjf.search.SearchContent;
import com.easyjf.web.tools.IPageList;
import com.easyjf.web.tools.PageList;
public class MyRssSearch {
?private String indexDir;?
?IndexReader ir;
?Searcher search;
?public IPageList search(String key,int pageSize,int currentPage)
?{
??IPageList pList=new PageList(new HitsQuery(doSearch(key)));
??pList.doList(pageSize,currentPage,"","",null);
??if(pList!=null)
??{??
???List list=pList.getResult();?
???if(list!=null){
???for(int i=0;i<list.size();i++)
???{
????list.set(i,lucene2searchObj((Document)list.get(i),key));
???}
???}
??}
??try{
??if(search!=null)search.close();
??if(ir!=null)ir.close();
??}
??catch(Exception e)
??{
???e.printStackTrace();
??}
??return pList;
?}
?private SearchContent lucene2searchObj(Document doc,String key)
?{
??SearchContent searchObj=new SearchContent();
??String title=doc.getField("title").stringValue();
??searchObj.setTitle(title.replaceAll(key,"<font color=red>"+key+"</font>"));??
??searchObj.setTvalue(doc.getField("cid").stringValue());??
??searchObj.setUrl(doc.getField("url").stringValue());
??searchObj.setSource(doc.getField("source").stringValue());??
??searchObj.setLastUpdated(doc.getField("inputTime").stringValue());?
??searchObj.setIntro(MyRssUtil.content2intro(doc.getField("content").stringValue(),key));??
??return searchObj;
?}
?public Hits doSearch(String key)
?{
??Hits hits=null;
??try{
??ir=IndexReader.open(indexDir);
??search=new IndexSearcher(ir);??
??String fields[]={"title","content"};
??QueryParser parser=new MultiFieldQueryParser(fields,new StandardAnalyzer());
??Query query=parser.parse(key);
??hits=search.search(query);???
??}
??catch(Exception e)
??{
???e.printStackTrace();
??}
??//System.out.println("搜索結果:"+hits.length());
??return hits;
?}
?
?public String getIndexDir() {
??return indexDir;
?}
?public void setIndexDir(String indexDir) {
??this.indexDir = indexDir;
?}
}
我們針對Lucene的的查詢結果Hits結構,寫了一個查詢器HitsQuery。代碼如下所示:
package com.easyjf.lucene;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.lucene.search.Hits;
import com.easyjf.web.tools.IQuery;
public class HitsQuery implements IQuery {
?private int begin=0;
?private int max=0;
?private Hits hits;
?public HitsQuery()
?{
??
?}
?public HitsQuery(Hits hits)
?{
??if(hits!=null)
??{
???this.hits=hits;???
???this.max=hits.length();
??}
?}
?public int getRows(String arg0) {
??// TODO Auto-generated method stub
??return (hits==null?0:hits.length());
?}
?public List getResult(String arg0) {
??// TODO Auto-generated method stub
??List list=new ArrayList();??
??for(int i=begin;i<(begin+max)&&(i<hits.length());i++)
??{
???try{
???list.add(hits.doc(i));
???}
???catch(Exception e)
???{
????e.printStackTrace();
???}
??}
??return list;
?}
?public void setFirstResult(int begin) {
??// TODO Auto-generated method stub
??this.begin=begin;
?}
?public void setMaxResults(int max) {
??// TODO Auto-generated method stub
??this.max=max;
?}
?public void setParaValues(Collection arg0) {
??// TODO Auto-generated method stub
??
?}
?public List getResult(String condition, int begin, int max) {
??// TODO Auto-generated method stub
??if((begin>=0)&&(begin<max))this.begin=begin;
??if(!(max>hits.length()))this.max=max;
??return getResult(condition);
?}
}
?
三、Web調用
下面我們來看看在Web中如果調用商業邏輯層的全文檢索功能。下面是處理用戶請請的Action中關于搜索部分的源碼:
package com.easyjf.news.action;
public class SearchAction implements IWebAction {?
public Page doSearch(WebForm form,Module module)throws Exception
{
?String key=CommUtil.null2String(form.get("v"));?
?key=URLDecoder.decode(URLEncoder.encode(key,"ISO8859_1"),"utf-8");
?form.set("v",key);
?form.addResult("v2",URLEncoder.encode(key,"utf-8"));
?if(key.getBytes().length>2){
?String orderBy=CommUtil.null2String(form.get("order"));?
?int currentPage=CommUtil.null2Int(form.get("page"));
?int pageSize=CommUtil.null2Int(form.get("pageSize"));??
?if(currentPage<1)currentPage=1;
?if(pageSize<1)pageSize=15;
?SearchEngine search=new SearchEngine(key,orderBy,pageSize,currentPage);
?search.getLuceneSearch().setIndexDir(Globals.APP_BASE_DIR+"/WEB-INF/index");
?search.doSearchByLucene();
?IPageList pList=search.getResult();
?if(pList!=null && pList.getRowCount()>0){
??form.addResult("list",pList.getResult());
??form.addResult("pages",new Integer(pList.getPages()));
??form.addResult("rows",new Integer(pList.getRowCount()));
??form.addResult("page",new Integer(pList.getCurrentPage()));
??form.addResult("gotoPageHTML",CommUtil.showPageHtml(pList.getCurrentPage(),pList.getPages()));
??}
?else
?{
??form.addResult("notFound","true");//找不到數據
?}?
?}
?else
??form.addResult("errMsg","您輸入的關鍵字太短!");
?form.addResult("hotSearch",SearchEngine.getHotSearch(20));
?return null;
}
}
其中調用的SearchEngine類中有關Lucene部分的源碼:
public class SearchEngine {
private MyRssSearch luceneSearch=new MyRssSearch();
public void doSearchByLucene()
{?
?SearchKey keyObj=readCache();?
?if(keyObj!=null){
??result=luceneSearch.search(key,pageSize,currentPage);??
??if(updateStatus){
??keyObj.setReadTimes(new Integer(keyObj.getReadTimes().intValue()+1));
??keyObj.update();
??}??
?}
?else//緩存中沒有該關鍵字信息,生成關鍵字搜索結果
?{?
??keyObj=new SearchKey();
??keyObj.setTitle(key);
??keyObj.setLastUpdated(new Date());
??keyObj.setReadTimes(new Integer(1));
??keyObj.setStatus(new Integer(0));
??keyObj.setSequence(new Integer(1));
??keyObj.setVdate(new Date());
??keyObj.save();?
??result=luceneSearch.search(key,pageSize,currentPage);;?
??
?}?
}
}
?
?