在本文章中Q你会学习到如何利用 Lucene 实现高搜烦功能以及如何利用 Lucene 来创?Web 搜烦应用E序。通过q些学习Q你可以利?Lucene 来创q搜烦应用E序?/p>
通常一?Web 搜烦引擎的架构分为前端和后端两部分,像下图中所C。在前端程中,用户在搜索引擎提供的界面中输入要搜烦的关键词Q这里提到的用户界面一般是一个带有输入框?Web 面Q然后应用程序将搜烦的关键词解析成搜索引擎可以理解的形式Qƈ在烦引文件上q行搜烦操作。在排序后,搜烦引擎q回搜烦l果l用戗在后端程中,|络爬虫或者机器h从因特网上获?Web 面Q然后烦引子pȝ解析q些 Web 面q存入烦引文件中。如果你惛_?Lucene 来创Z?Web 搜烦应用E序Q那么它的架构也和上面所描述的类|如下图中所C?/p>
Figure 1. Web 搜烦引擎架构
Lucene 支持多种形式的高U搜索,我们在这一部分中会q行探讨Q然后我会?Lucene ?API 来演C如何实现这些高U搜索功能?/p>
大多数的搜烦引擎都会提供布尔操作W让用户可以l合查询Q典型的布尔操作W有 AND, OR, NOT。Lucene 支持 5 U布操作符Q分别是 AND, OR, NOT, ?+), ?-)。接下来我会讲述每个操作W的用法?
接下来我们看一下如何利?Lucene 提供?API 来实现布查询。下面代?昄了如果利用布操作符q行查询的过E?/p>
清单1Q用布操作符
//Test boolean operator public void testOperator(String indexDirectory) throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"Java AND Lucene", "Java NOT Lucene", "Java OR Lucene", "+Java +Lucene", "+Java -Lucene"}; Analyzer language = new StandardAnalyzer(); Query query; for(int i = 0; i < searchWords.length; i++){ query = QueryParser.parse(searchWords[i], "title", language); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
Lucene 支持域搜索,你可以指定一ơ查询是在哪些域(Field)上进行。例如,如果索引的文档包含两个域Q?code>Title ?Content
Q你可以用查?“Title: Lucene AND Content: Java” 来返回所有在 Title 域上包含 Lucene q且?Content 域上包含 Java 的文档。下面代?昄了如何利?Lucene ?API 来实现域搜烦?
//Test field search public void testFieldSearch(String indexDirectory) throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String searchWords = "title:Lucene AND content:Java"; Analyzer language = new StandardAnalyzer(); Query query = QueryParser.parse(searchWords, "title", language); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords); } |
Lucene 支持两种通配W:问号Q?Q和星号Q?Q。你可以使用问号Q?Q来q行单字W的通配W查询,或者利用星P*Q进行多字符的通配W查询。例如,如果你想搜烦 tiny 或?tonyQ你可以用查询语?“t?ny”Q如果你x?Teach, Teacher ?TeachingQ你可以用查询语?“Teach*”。下面代?昄了通配W查询的q程?
//Test wildcard search public void testWildcardSearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"tex*", "tex?", "?ex*"}; Query query; for(int i = 0; i < searchWords.length; i++){ query = new WildcardQuery(new Term("title",searchWords[i])); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
Lucene 提供的模p查询基于编辑距ȝ?Edit distance algorithm)。你可以在搜索词的尾部加上字W?~ 来进行模p查询。例如,查询语句 “think~” q回所有包含和 think cM的关键词的文档。下面代码显CZ如果利用 Lucene ?API q行模糊查询的代码?
//Test fuzzy search public void testFuzzySearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"text", "funny"}; Query query; for(int i = 0; i < searchWords.length; i++){ query = new FuzzyQuery(new Term("title",searchWords[i])); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
范围搜烦匚w某个域上的值在一定范围的文档。例如,查询 “age:[18 TO 35]” q回所?age 域上的值在 18 ?35 之间的文档。下面代码显CZ利用 Lucene ?API q行q回搜烦的过E?
//Test range search public void testRangeSearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); Term begin = new Term("birthDay","20000101"); Term end = new Term("birthDay","20060606"); Query query = new RangeQuery(begin,end,true); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results is returned"); } |
接下来我们开发一?Web 应用E序利用 Lucene 来检索存攑֜文g服务器上?HTML 文档。在开始之前,需要准备如下环境:
q个例子使用 Eclipse q行 Web 应用E序的开发,最l这?Web 应用E序跑在 Tomcat 5.0 上面。在准备好开发所必需的环境之后,我们接下来进?Web 应用E序的开发?
在我们的设计中,把该pȝ分成如下四个子系l:
下图昄了我们设计的详细信息Q我们将用户接口子系l放?webContent 目录下面。你会看C个名?search.jsp 的页面在q个文g多w面。请求管理子pȝ在包 sample.dw.paper.lucene.servlet
下面Q类 SearchController
负责功能的实现。搜索子pȝ攑֜?sample.dw.paper.lucene.search
当中Q它包含了两个类Q?code>SearchManager ?SearchResultBean
Q第一个类用来实现搜烦功能Q第二个cȝ来描q搜索结果的l构。烦引子pȝ攑֜?sample.dw.paper.lucene.index
当中。类 IndexManager
负责?HTML 文g创徏索引。该子系l利用包 sample.dw.paper.lucene.util
里面的类 HTMLDocParser
提供的方?getTitle
?getContent
来对 HTML 面q行解析?
在分析了pȝ的架构设计之后,我们接下来看pȝ实现的详l信息?
q个JSP的第二部分负责显C搜索结果给用户Q如图下图所C:
SearchController
?servlet 用来实现该子pȝ。下面代码给Zq个cȝ源代码?
package sample.dw.paper.lucene.servlet; import java.io.IOException; import java.util.List; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import sample.dw.paper.lucene.search.SearchManager; /** * This servlet is used to deal with the search request * and return the search results to the client */ public class SearchController extends HttpServlet{ private static final long serialVersionUID = 1L; public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ String searchWord = request.getParameter("searchWord"); SearchManager searchManager = new SearchManager(searchWord); List searchResult = null; searchResult = searchManager.search(); RequestDispatcher dispatcher = request.getRequestDispatcher("search.jsp"); request.setAttribute("searchResult",searchResult); dispatcher.forward(request, response); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doPost(request, response); } } |
在代码中Q?code>doPost Ҏ从客L获取搜烦词ƈ创徏c?SearchManager
的一个实例,其中c?SearchManager
在搜索子pȝ中进行了定义。然后,SearchManager
的方?search 会被调用。最后搜索结果被q回到客L?
SearchResultBean
。第一个类用来实现搜烦功能Q第二个cL个JavaBeanQ用来描q搜索结果的l构。下面代码给Zc?SearchManager
的源代码?
package sample.dw.paper.lucene.search; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.queryParser.ParseException; 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 sample.dw.paper.lucene.index.IndexManager; /** * This class is used to search the * Lucene index and return search results */ public class SearchManager { private String searchWord; private IndexManager indexManager; private Analyzer analyzer; public SearchManager(String searchWord){ this.searchWord = searchWord; this.indexManager = new IndexManager(); this.analyzer = new StandardAnalyzer(); } /** * do search */ public List search(){ List searchResult = new ArrayList(); if(false == indexManager.ifIndexExist()){ try { if(false == indexManager.createIndex()){ return searchResult; } } catch (IOException e) { e.printStackTrace(); return searchResult; } } IndexSearcher indexSearcher = null; try{ indexSearcher = new IndexSearcher(indexManager.getIndexDir()); }catch(IOException ioe){ ioe.printStackTrace(); } QueryParser queryParser = new QueryParser("content",analyzer); Query query = null; try { query = queryParser.parse(searchWord); } catch (ParseException e) { e.printStackTrace(); } if(null != query >> null != indexSearcher){ try { Hits hits = indexSearcher.search(query); for(int i = 0; i < hits.length(); i ++){ SearchResultBean resultBean = new SearchResultBean(); resultBean.setHtmlPath(hits.doc(i).get("path")); resultBean.setHtmlTitle(hits.doc(i).get("title")); searchResult.add(resultBean); } } catch (IOException e) { e.printStackTrace(); } } return searchResult; } } |
在上面代码,注意到在q个c里面有三个U有属性。第一个是 searchWord
Q代表了来自客户端的搜烦词。第二个?indexManager
Q代表了在烦引子pȝ中定义的c?IndexManager
的一个实例。第三个?analyzer
Q代表了用来解析搜烦词的解析器。现在我们把注意力放在方?search
上面。这个方法首先检查烦引文件是否已l存在,如果已经存在Q那么就在已l存在的索引上进行检索,如果不存在,那么首先调用c?IndexManager
提供的方法来创徏索引Q然后在新创建的索引上进行检索。搜索结果返回后Q这个方法从搜烦l果中提取出需要的属性ƈ为每个搜索结果生成类 SearchResultBean
的一个实例。最后这?SearchResultBean
的实例被攑ֈ一个列表里面ƈq回l请求管理器?/p>
在类 SearchResultBean
中,含有两个属性,分别?htmlPath
?htmlTitle
Q以及这个两个属性的 get ?set Ҏ。这也意味着我们的搜索结果包含两个属性:htmlPath
?htmlTitle
Q其?htmlPath
代表?HTML 文g的\径,htmlTitle
代表?HTML 文g的标题?
IndexManager
用来实现q个子系l?下面代码l出了这个类的源代码?
package sample.dw.paper.lucene.index; import java.io.File; import java.io.IOException; import java.io.Reader; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import sample.dw.paper.lucene.util.HTMLDocParser; /** * This class is used to create an index for HTML files * */ public class IndexManager { //the directory that stores HTML files private final String dataDir = "c:\\dataDir"; //the directory that is used to store a Lucene index private final String indexDir = "c:\\indexDir"; /** * create index */ public boolean createIndex() throws IOException{ if(true == ifIndexExist()){ return true; } File dir = new File(dataDir); if(!dir.exists()){ return false; } File[] htmls = dir.listFiles(); Directory fsDirectory = FSDirectory.getDirectory(indexDir, true); Analyzer analyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(fsDirectory, analyzer, true); for(int i = 0; i < htmls.length; i++){ String htmlPath = htmls[i].getAbsolutePath(); if(htmlPath.endsWith(".html") || htmlPath.endsWith(".htm")){ addDocument(htmlPath, indexWriter); } } indexWriter.optimize(); indexWriter.close(); return true; } /** * Add one document to the Lucene index */ public void addDocument(String htmlPath, IndexWriter indexWriter){ HTMLDocParser htmlParser = new HTMLDocParser(htmlPath); String path = htmlParser.getPath(); String title = htmlParser.getTitle(); Reader content = htmlParser.getContent(); Document document = new Document(); document.add(new Field("path",path,Field.Store.YES,Field.Index.NO)); document.add(new Field("title",title,Field.Store.YES,Field.Index.TOKENIZED)); document.add(new Field("content",content)); try { indexWriter.addDocument(document); } catch (IOException e) { e.printStackTrace(); } } /** * judge if the index exists already */ public boolean ifIndexExist(){ File directory = new File(indexDir); if(0 < directory.listFiles().length){ return true; }else{ return false; } } public String getDataDir(){ return this.dataDir; } public String getIndexDir(){ return this.indexDir; } } |
q个cd含两个私有属性,分别?dataDir
?indexDir
?code>dataDir 代表存放{待q行索引?HTML 面的\径,indexDir
代表了存?Lucene 索引文g的\径。类 IndexManager
提供了三个方法,分别?createIndex
, addDocument
?ifIndexExist
。如果烦引不存在的话Q你可以使用Ҏ createIndex
dZ个新的烦引,用方?addDocument
d一个烦引上d文档。在我们的场景中Q一个文档就是一?HTML 面。方?addDocument
会调用由c?HTMLDocParser
提供的方法对 HTML 文档q行解析。你可以使用最后一个方?ifIndexExist
来判?Lucene 的烦引是否已l存在?
现在我们来看一下放在包 sample.dw.paper.lucene.util
里面的类 HTMLDocParser
。这个类用来?HTML 文g中提取出文本信息。这个类包含三个ҎQ分别是 getContent
Q?code>getTitle ?getPath
。第一个方法返回去除了 HTML 标记的文本内容,W二个方法返?HTML 文g的标题,最后一个方法返?HTML 文g的\径。下面代码给Zq个cȝ源代码?
package sample.dw.paper.lucene.util; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import org.apache.lucene.demo.html.HTMLParser; public class HTMLDocParser { private String htmlPath; private HTMLParser htmlParser; public HTMLDocParser(String htmlPath){ this.htmlPath = htmlPath; initHtmlParser(); } private void initHtmlParser(){ InputStream inputStream = null; try { inputStream = new FileInputStream(htmlPath); } catch (FileNotFoundException e) { e.printStackTrace(); } if(null != inputStream){ try { htmlParser = new HTMLParser(new InputStreamReader(inputStream, "utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } public String getTitle(){ if(null != htmlParser){ try { return htmlParser.getTitle(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } return ""; } public Reader getContent(){ if(null != htmlParser){ try { return htmlParser.getReader(); } catch (IOException e) { e.printStackTrace(); } } return null; } public String getPath(){ return this.htmlPath; } } |
现在我们可以?Tomcat 5.0 上运行开发好的应用程序?
现在我们已经成功的完成了CZ目的开发,q成功的用Lucene实现了搜索和索引功能。你可以下蝲q个目的源代码Q?a title="下蝲" href="http://www.tkk7.com/Files/szhswl/wa-lucene2_source_code.zip">下蝲Q?
Lucene 提供了灵zȝ接口使我们更加方便的设计我们?Web 搜烦应用E序。如果你惛_你的应用E序中加入搜索功能,那么 Lucene 是一个很好的选择。在设计你的下一个带有搜索功能的应用E序的时候可以考虑使用 Lucene 来提供搜索功能?br />
本文摘自Qhttp://www.ibm.com/developerworks/cn/web/wa-lucene2/
${hit.highlightedText['content']?if_exists}
本文转自:http://jdkcn.com/entry/howto-add-highlight-in-compass.html
IndexSearcher的常用方法有
search(Query q);
search(Query q,Filter filter);
search(Query q,Sort sort);
search(Query q,Filter filter,Sort sort);
Hits的常用方法有
doc(i); //得到Wi个Document
id(i); //得到Wi个Document在lucene文g中的id?br />
length(); //l果集的数量
score(i); //Wi个Doucment的文档得分,默认昄方式为scoreD高,排得前。score取?-1之间
如果x高score的倹{可以有建立索引时设|,用Field.setBoost(Float f)Ҏ
Field f = new Field(fieldname,value,store,tokenized);
f.setBoost(5f);
在lucene中,document ID 小Q查询时所需旉短Q因为Hits的内部缓存机制?/p>
Lunece的常用搜?/p>
一、TermQuery 词条搜烦
Query query = new TermQuery(new Term(fieldname,value));
二、BooleanQuery 布尔搜烦
建立二个TermQuery
Query q1 = new TermQuery(new Term(fieldname1,value1));
Query q2 = new TermQuery(new Term(fieldname2,value2));
建立BooleanQuery对象
BooleanQuery query = new BooleanQuery();
query.add(q1,BooleanClause.Occue.MUST);
query.add(q2,BooleanClause.Occue.MUST);
BooleanClause.Occue 有三个静态?br />
MUSTQMUST_NOTQSHOULD
must&&must = (AnB)
must&&must_not = (A-(AnB))
should&&should = (AuB)
三、RangeQuery 范围搜烦
RangeQuery query = new RangeQuery(begin,end,false);
begin = new Term(fieldname,value);
end = new Term(fieldname,value);
false 表示开区间 不包?(begin,end) true 表示闭区?包括 [begin,end]
四、PrefixQuery 前缀搜烦
PrefixQuery query = new PrefixQuery(new Term(fieldname,value));
五、PhraseQuery 短语搜烦
PhraseQuery query = new PhraseQuery();
query.add(new Term(fieldname,value));
query.add(new Term(fieldname,value));
q可以设|坡度,query.setSlop(int n),默认?如查?#8220;钢铁”Q可以用
query.add(new Term(fieldname,”?#8221;));
query.add(new Term(fieldname,”?#8221;));
如想?#8220;钢和?#8221;Q?#8220;钢与?#8221;也查询出来。可以加上query.setSlop(1);
六、MultiPhraseQuery 多短语搜?br />
MultiPhraseQuery query = new MultiPharseQuery();
//加入短语的前~
query.add(new Term(fieldname,value));
//加入短语的后~
query.add(new Term[] {new Term(fieldname,value), new Term(fieldname,value)});
七、FuzzyQuery 模糊搜烦
FuzzyQuery query = new FuzzyQuery(new Term(filed,value));
它的三个构造函?br />
FuzzyQuery(Term t);
FuzzyQuery(Term t,float 0.5f); 怼度?-1之间
FuzzyQuery(Term t,float 0.5f,int prefixLength);前缀必须相同的长?br />
本文转自:http://job5156.xicp.net/?p=72
Lucene(发音?['lusen] )是一个非怼U的开源的全文搜烦引擎,我们可以在它的上面开发出各种全文搜烦的应用来。Lucene在国外有很高的知名度Q现在已l是Apache的顶U项目,在国内,Lucene的应用也来多?/p>
Lucene的算法原理:
Lucene是一个高性能的java全文索工具包Q它使用的是倒排文g索引l构。该l构及相应的生成法如下Q?
0Q设有两文??
文章1的内容ؓQTom lives in Guangzhou,I live in Guangzhou too.
文章2的内容ؓQHe once lived in Shanghai.
1)全文分析Q由于lucene是基于关键词索引和查询的Q首先我们要取得q两文章的关键词,通常我们需要如下处理措?
a.我们现在有的是文章内容,即一个字W串Q我们先要找出字W串中的所有单词,卛_词。英文单词由于用I格分隔Q比较好处理。中文单词间是连在一L需要特D的分词处理?
b.文章中的”in”, “once” “too”{词没有什么实际意义,中文中的“?#8221;“?#8221;{字通常也无具体含义Q这些不代表概念的词可以qo?
c.用户通常希望?#8220;He”时能把含“he”Q?#8220;HE”的文章也扑և来,所以所有单词需要统一大小写?
d.用户通常希望?#8220;live”时能把含“lives”Q?#8220;lived”的文章也扑և来,所以需要把“lives”Q?#8220;lived”q原?#8220;live”
e.文章中的标点W号通常不表C某U概念,也可以过滤掉
在lucene中以上措施由Analyzercd?/font>
l过上面处理?
文章1的所有关键词为:[tom] [live] [guangzhou] [i] [live] [guangzhou]
文章2的所有关键词为:[he] [live] [shanghai]
2) 倒排索引Q有了关键词后,我们可以徏立倒排索引了。上面的对应关系是:“文章?#8221;?#8220;文章中所有关键词”。倒排索引把这个关pd过来,变成Q?#8220;关键?#8221;?#8220;拥有该关键词的所有文章号”?/font>文章1Q?l过倒排后变?
关键?文章?
guangzhou 1
he 2
i 1
live 1,2
shanghai 2
tom 1
通常仅知道关键词在哪些文章中出现q不够,我们q需要知道关键词在文章中出现ơ数和出现的位置Q通常有两U位|:a)字符位置Q即记录该词是文章中W几个字W(优点是关键词亮显时定位快Q;b)关键词位|,卌录该词是文章中第几个关键词(优点是节U烦引空间、词l(phaseQ查询快Q,lucene中记录的是q种位置?
加上“出现频率”?#8220;出现位置”信息后,我们的烦引结构变为:
关键?/td> | 文章?/td> | [出现频率] | 出现位置 |
guangzhou | 1 | [2] | 3Q? |
he | 2 | [1] | 1 |
i | 1 | [1] | 4 |
live | 1 | [2] | 2Q? |
2 | [1] | 2 | |
shanghai | 2 | [1] | 3 |
tom | 1 | [1] | 1 |
以live q行Z我们说明一下该l构Qlive在文?中出C2ơ,文章2中出C一ơ,它的出现位置?#8220;2,5,2”q表CZ么呢Q我们需要结合文章号和出现频率来分析Q文?中出C2ơ,那么“2,5”pClive在文?中出现的两个位置Q文?中出C一ơ,剩下?#8220;2”pClive是文?中第 2个关键字?
以上是lucene索引l构中最核心的部分。我们注意到关键字是按字W顺序排列的Qlucene没有使用B树结构)Q?/font>因此lucene可以?font color="#008080">二元搜烦法快速定位关键词?
实现?lucene上面三列分别作?font color="#800000">词典文gQTerm DictionaryQ?font color="#800000">频率文g(frequencies)?font color="#800000">位置文g (positions)保存。其?font color="#008080">词典文g不仅保存有每个关键词Q还保留了指向频率文件和位置文g的指针,通过指针可以扑ֈ该关键字的频率信息和位置信息?
Lucene中用了field的概念,用于表达信息所在位|(如标题中Q文章中Qurl中)Q?/font>在徏索引中,?font color="#008080">field信息也记录在词典文g?/font>Q每个关键词都有一个field信息(因ؓ每个关键字一定属于一个或多个field)?
Z减小索引文g的大,Lucene对烦引还使用?font color="#800000">压羃技?/font>。首先,对词典文件中的关键词q行了压~,关键词压~ؓ<前缀长度Q后~>Q例如:当前词ؓ“阿拉伯语”Q上一个词?#8220;阿拉?#8221;Q那?#8220;阿拉伯语”压羃?lt;3Q语>。其ơ大量用到的是对数字的压~,数字只保存与上一个值的差|q样可以减小数字的长度,q而减保存该数字需要的字节敎ͼ。例如当前文章号?6389Q不压羃要用3个字节保存)Q上一文章h16382Q压~后保存7Q只用一个字节)?注意?#8220;上一个词”。由于词典是按顺序排列的Q这U压~方法的效果会非常显著?/font>
下面我们可以通过对该索引的查询来解释一下ؓ什么要建立索引?
假设要查询单?“live”Qlucene先对词典二元查找、找到该词,通过指向频率文g的指针读出所有文章号Q然后返回结果。词兔R常非常,因而,整个q程的时间是毫秒U的?
而用普通的序匚w法Q不建烦引,而是Ҏ有文章的内容q行字符串匹配,q个q程会相当~慢Q当文章数目很大Ӟ旉往往是无法忍受的?/p>
全文索框架的实现机制Q?/strong>
Lucene的API接口设计的比较通用Q输入输出结构都很像数据库的?=>记录==>字段Q所以很多传l的应用的文件、数据库{都可以比较方便的映到Lucene的存储结?接口中。M上看Q可以先把Lucene当成一个支持全文烦引的数据库系l?/p>
比较一下Lucene和数据库Q?/p>
Lucene | 数据?/td> |
索引数据源:doc(field1,field2...) doc(field1,field2...) \ indexer / l果输出QHits(doc(field1,field2) doc(field1...)) |
索引数据源:record(field1,field2...) record(field1..) \ SQL: insert/ l果输出Qresults(record(field1,field2..) record(field1...)) |
DocumentQ一个需要进行烦引的“单元,一个Document由多个字D늻? |
RecordQ记录,包含多个字段 |
FieldQ字D?/p> |
FieldQ字D?/td> |
HitsQ查询结果集Q由匚w的Documentl成 |
RecordSetQ查询结果集Q由多个Recordl成 |
全文?≠ like "%keyword%"
׃数据库烦引不是ؓ全文索引设计的,因此Q用like "%keyword%"Ӟ数据库烦引是不v作用的,在用like查询Ӟ搜烦q程又变成类g一页M的遍历过E了Q所以对于含有模p查询的数据库服务来_LIKEҎ能的危x极大的。如果是需要对多个关键词进行模p匹配:like"%keyword1%" and like "%keyword2%" ...其效率也可惌知了?/p>
通常比较厚的书籍后面常常附关键词索引表(比如Q北京:12, 34,上vQ?,77?#8230;…Q,它能够帮助读者比较快地找到相兛_容的늠。而数据库索引能够大大提高查询的速度原理也是一P惛_一下通过书后面的索引查找的速度要比一一地d定w多少?#8230;…而烦引之所以效率高Q另外一个原因是它是排好序的。对于检索系l来说核心是一个排序问题?/p>
所以徏立一个高效检索系l的关键是徏立一个类gU技索引一L反向索引机制Q将数据源(比如多篇文章Q排序顺序存储的同时Q有另外一个排好序的关键词列表Q用于存储关键词==>文章映射关系Q利用这L映射关系索引Q[关键?=>出现关键词的文章~号Q出现次敎ͼ甚至包括位置Qv始偏U量Q结束偏U量Q,出现频率]Q检索过E就是把模糊查询变成多个可以利用索引的精查询的逻辑l合的过E。从而大大提高了多关键词查询的效率,所以,全文索问题归l到最后是一个排序问题?/p>
由此可以看出模糊查询相对数据库的_查询是一个非怸定的问题,q也是大部分数据库对全文索支持有限的原因。Lucene最核心的特征是通过Ҏ的烦引结构实C传统数据库不擅长的全文烦引机Ӟq提供了扩展接口Q以方便针对不同应用的定制?/p>
可以通过一下表格对比一下数据库的模p查询:
Lucene全文索引引擎 | 数据?/td> | |
索引 | 数据源中的数据都通过全文索引一一建立反向索引 | 对于LIKE查询来说Q数据传l的索引是根本用不上的。数据需要逐个便利记录q行GREP式的模糊匚wQ比有烦引的搜烦速度要有多个数量U的下降?/td> |
匚w效果 | 通过词元(term)q行匚wQ通过语言分析接口的实玎ͼ可以实现对中文等非英语的支持?/td> | 使用Qlike "%net%" 会把netherlands也匹配出来, 多个关键词的模糊匚wQ用like "%com%net%"Q就不能匚w词序颠倒的xxx.net..xxx.com |
匚w?/td> | 有匹配度法Q将匚wE度Q相似度Q比较高的结果排在前面?/td> | 没有匚wE度的控Ӟ比如有记录中net出现5词和出现1ơ的Q结果是一L |
l果输出 | 通过特别的算法,最匚w度最高的?00条结果输出,l果集是~冲式的批量读取的?/td> | q回所有的l果集,在匹配条目非常多的时候(比如上万条)需要大量的内存存放q些临时l果集?/td> |
可定制?/td> | 通过不同的语a分析接口实现Q可以方便的定制出符合应用需要的索引规则Q包括对中文的支持) | 没有接口或接口复杂,无法定制 |
l论 | 高负载的模糊查询应用Q需要负责的模糊查询的规则,索引的资料量比较?/td> | 使用率低Q模p匹配规则简单或者需要模p查询的资料量少 |
全文索和数据库应用最大的不同在于Q让最相关?/span> ?00条结果满?8%以上用户的需求?/font>
Lucene的创C处:
大部分的搜烦Q数据库Q引擎都是用B树结构来l护索引Q烦引的更新会导致大量的IO操作QLucene在实CQ对此稍微有所改进Q不是维护一个烦引文Ӟ而是在扩展烦引的时候不断创建新的烦引文Ӟ然后定期的把q些新的烦引文件合q到原先的大索引中(针对不同的更新策略,Ҏ的大可以调_Q这样在不媄响检索的效率的前提下Q提高了索引的效率?/p>
Lucene和其他一些全文检索系l?应用的比较:
Lucene | 其他开源全文检索系l?/td> | |
增量索引和批量烦?/td> | 可以q行增量的烦?Append)Q可以对于大量数据进行批量烦引,q且接口设计用于优化扚w索引和小扚w的增量烦引?/td> | 很多pȝ只支持批量的索引Q有时数据源有一点增加也需要重建烦引?/td> |
数据?/td> | Lucene没有定义具体的数据源Q而是一个文档的l构Q因此可以非常灵zȝ适应各种应用Q只要前端有合适的转换器把数据源{换成相应l构Q?/td> | 很多pȝ只针对网,~Z其他格式文档的灵zL?/td> |
索引内容抓取 | Lucene的文档是由多个字D늻成的Q甚臛_以控刉些字D需要进行烦引,那些字段不需要烦引,q一步烦引的字段也分为需要分词和不需要分词的cdQ?br /> 需要进行分词的索引Q比如:标题Q文章内容字D?br /> 不需要进行分词的索引Q比如:作?日期字段 | ~Z通用性,往往文档整个烦引了 |
语言分析 | 通过语言分析器的不同扩展实现Q?br />
可以qo掉不需要的词:an the of {, 西文语法分析Q将jumps jumped jumper都归l成jumpq行索引/?br /> 非英文支持:对亚z语aQ阿拉伯语言的烦引支?/td> | ~Z通用接口实现 |
查询分析 | 通过查询分析接口的实玎ͼ可以定制自己的查询语法规则: 比如Q?多个关键词之间的 + - and or关系{?/td> | 功能较强?/td> |
q发讉K | 能够支持多用L使用 | 功能较强?/td> |
关于亚洲语言的的切分词问?Word Segment)
对于中文来说Q全文烦引首先还要解决一个语a分析的问题,对于英文来说Q语句中单词之间是天焉过I格分开的,但亚z语a的中日韩文语句中的字是一个字挨一个,所有,首先要把语句中按“?#8221;q行索引的话Q这个词如何切分出来是一个很大的问题?br />
首先Q肯定不能用单个字符?si-gram)为烦引单元,否则?#8220;上v”Ӟ不能让含?#8220;上”也匹配?br />
但一句话Q?#8220;北京天安?#8221;Q计机如何按照中文的语a习惯q行切分呢?
“北京 天安?#8221; q是“??天安?#8221;Q让计算够按照语a习惯q行切分Q往往需要机器有一个比较丰富的词库才能够比较准的识别句中的单词?br />
另外一个解决的办法是采用自动切分算法:单词按?元语?bigram)方式切分出来Q比如:
"北京天安? ==> "北京 京天 天安 安门"?br />
q样Q在查询的时候,无论是查?北京" q是查询"天安?Q将查询词组按同L规则q行切分Q?北京"Q?天安安门"Q多个关键词之间按与"and"的关pȝ合,同样能够正确地映到相应的烦引中。这U方式对于其他亚z语aQ韩文,日文都是通用的?br />
Z自动切分的最大优Ҏ没有词表l护成本Q实现简单,~点是烦引效率低Q但对于中小型应用来_Z2元语法的切分q是够用的。基?元切分后的烦引一般大和源文件差不多Q而对于英文,索引文g一般只有原文g?0%-40%不同?
自动切分 | 词表切分 | |
实现 | 实现非常?/td> | 实现复杂 |
查询 | 增加了查询分析的复杂E度 | 适于实现比较复杂的查询语法规?/td> |
存储效率 | 索引冗余大,索引几乎和原文一样大 | 索引效率高,为原文大的30Q左?/td> |
l护成本 | 无词表维护成?/td> | 词表l护成本非常高:中日韩等语言需要分别维护?br /> q需要包括词频统计等内容 |
适用领域 | 嵌入式系l:q行环境资源有限 分布式系l:无词表同步问?br /> 多语a环境Q无词表l护成本 |
Ҏ询和存储效率要求高的专业搜烦引擎 |
目前比较大的搜烦引擎的语a分析法一般是Z以上2个机制的l合。关于中文的语言分析法Q大家可以在Google查关键词"wordsegment search"能找到更多相关的资料?/p>
Lucene的结构框Ӟ
注意QLucene中的一些比较复杂的词法分析是用JavaCC生成的(JavaCCQJavaCompilerCompilerQ纯Java的词法分析生成器Q,所以如果从源代码编译或需要修改其中的QueryParser、定制自q词法分析器,q需要从https://javacc.dev.java.net/下蝲javacc?br />
lucene的组成结构:对于外部应用来说索引模块(index)和检索模?search)是主要的外部应用入口?
org.apache.Lucene.search/ | 搜烦入口 |
org.apache.Lucene.index/ | 索引入口 |
org.apache.Lucene.analysis/ | 语言分析?/td> |
org.apache.Lucene.queryParser/ | 查询分析?/td> |
org.apache.Lucene.document/ | 存储l构 |
org.apache.Lucene.store/ | 底层IO/存储l构 |
org.apache.Lucene.util/ | 一些公用的数据l构 |
从Lucene学到更多Q?/strong> 本文转自Q?a >http://www.chedong.com/tech/lucene.html
Luene的确是一个面对对象设计的典范?/p>
q些优点都是非常值得在以后的开发中学习借鉴的。作Z个通用工具包,Lunece的确l予了需要将全文索功能嵌入到应用中的开发者很多的便利?br />
此外Q通过对Lucene的学习和使用Q我也更深刻地理解了Z么很多数据库优化设计中要求,比如Q?/p>