??xml version="1.0" encoding="utf-8" standalone="yes"?> 1. 基本介绍Q?/strong> paoding QLucene中文分词“庖丁解牛” Paoding Analysis 2. 开发者及开发活跃度Q?/strong> paoding Q?a style="white-space: nowrap" target="_blank">qieqie.wangQ?google code 上最后一ơ代码提交:2008-06-12Qsvn 版本?132 3. 用户自定义词库: paoding Q支持不限制个数的用戯定义词库Q纯文本格式Q一行一词,使用后台U程词库的更新Q自动编译更新过的词库到二进制版本,q加?br />
imdict Q暂时不支持用户自定义词库。但 原版 ICTCLAS 支持。支持用戯定义 stop words 4. 速度Q基于官方介l,非自己测试) paoding Q在PIII 1G内存个h机器上,1U?/strong> 可准分?100?/strong> 汉字 5. 法和代码复杂度 paoding Qsvn src 目录一?.3MQ?个properties文gQ?8个java文gQ?895 行。用不用的 Knife 切不同类型的,不算很复杂?br />
imdict Q词?6.7MQ这个词库是必须的)Qsrc 目录 152kQ?0个java文gQ?399行。?ICTCLAS HHMM隐马科夫模型,“利用大量语料库的训练来统计汉语词汇的词频和蟩转概率,从而根据这些统计结果对整个汉语句子计算最似然(likelihood)的切?#8221; 6. 文档 paoding Q几乎无。代码里有一些注释,但因为实现比较复杂,M码还是有一些难度的?br />
imdict Q?几乎无?ICTCLAS 也没有详l的文档QHHMM隐马科夫模型的数学性太强,不太好理解?br />
mmseg4j Q?MMSeg 法 是英文的Q但原理比较单。实C比较清晰?br />
ik Q?有一个pdf使用手册Q里面有使用CZ和配|说明?/p>
7. 其它 paoding Q引入隐喻,设计比较合理。search 1.0 版本q的这个。主要优势在于原生支持词库更新检。主要劣势ؓ作者已l不更新甚至不维护了?br />
imdict Q进入了 lucene trunkQ原?ictclas 在各U评中都有不错的表玎ͼ有坚实的理论基础Q不是个人山寨。缺点ؓ暂时不支持用戯库?br />
mmseg4j Q?在complex基础上实C最多分?max-word)Q但是还不成熟,q有很多需要改q的地方?br />
ik Q?nbsp; 针对Lucene全文索优化的查询分析器IKQueryParser 8. l论 个h觉得Q可以在 mmseg4j ?paoding 中选一个。关于这两个分词效果的对比,可以参考: http://blog.chenlb.com/2009/04/mmseg4j-max-word-segment-compare-with-paoding-in-effect.html 或者自己再包装一下,?paoding 的词库更新检做一个单独的模块实现Q然后就可以在所有基于词库的分词法之间无缝切换了?/p>
psQ对不同?field 使用不同的分词器是一个可以考虑的方法。比?tag 字段Q就应该使用一个最单的分词器,按空格分词就可以了?/p>
imdict Qimdict词典所采用的智能中文分词程?br />
mmseg4j Q??Chih-Hao Tsai ?MMSeg 法 实现的中文分词器
ik Q采用了Ҏ?#8220;正向q代最l粒度切分算?#8220;Q多子处理器分析模式
imdict Q?a target="_blank">XiaoPingGaoQ?q入?lucene contributeQlucene trunk ?contrib/analyzers/smartcn/ 最后一ơ提交:2009-07-24Q?br />
mmseg4j Q?a style="white-space: nowrap" target="_blank">chenlb2008Qgoogle code ?2009-08-03 Q昨天)Q版本号 57Qlog为:mmseg4j-1.7 创徏分支
ik Q?a style="white-space: nowrap" target="_blank">linliangyi2005Qgoogle code ?2009-07-31Q版本号 41
mmseg4j Q自带sogou词库Q支持名?wordsxxx.dicQ?utf8文本格式的用戯定义词库Q一行一词。不支持自动?-Dmmseg.dic.path
ik Q?支持apiU的用户词库加蝲Q和配置U的词库文g指定Q无 BOM ?UTF-8 ~码Q\r\n 分割。不支持自动?/p>
imdict Q?strong>483.64 (字节/U?Q?strong>259517(汉字/U?
mmseg4j Q?complex 1200kb/s左右, simple 1900kb/s左右
ik Q具?0万字/U的高速处理能?/p>
mmseg4j Q?svn src 目录一?132kQ?3个java文gQ?089行?a target="_blank">MMSeg 法 Q有点复杂?br />
ik Q?svn src 目录一?.6M(词典文g也在里面)Q?2个java文gQ?217行。多子处理器分析Q跟paodingcMQ歧义分析算法还没有弄明白?/p>
package com.rain.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();
}
public 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;
}
}
描述搜烦l果的结构实体Bean
package com.rain.search;
public class SearchResultBean {
private String htmlPath;
private String htmlTitle;
public String getHtmlPath() {
return htmlPath;
}
public void setHtmlPath(String htmlPath) {
this.htmlPath = htmlPath;
}
public String getHtmlTitle() {
return htmlTitle;
}
public void setHtmlTitle(String htmlTitle) {
this.htmlTitle = htmlTitle;
}
}
索引子系l的实现
package com.rain.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.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.document.Field;
import com.rain.util.HTMLDocParser;
public class IndexManager {
//the directory that stores HTML files
private final String dataDir="E:\\dataDir";
//the directory that is used to store a Lucene index
private final String indexDir="E:\\indexDir";
public boolean creatIndex()throws IOException{
if(true==inIndexExist()){
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;
}
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();
}
}
public String getDataDir(){
return this.dataDir;
}
public String getIndexDir(){
return this.indexDir;
}
public boolean inIndexExist(){
File directory=new File(indexDir);
if(0<directory.listFiles().length){
return true;
}else{
return false;
}
}
}
搜烦功能的实?br />package com.rain.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 com.rain.index.IndexManager;
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.inIndexExist()){
try{
if(false==indexManager.creatIndex()){
return searchResult;
}
}catch(IOException e){
e.printStackTrace();
return searchResult;
}
}
IndexSearcher indexSearcher=null;
try{
indexSearcher=new IndexSearcher(indexManager.getIndexDir());
}catch(IOException e){
e.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;
}
}
h理器的实现
package com.rain.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 com.rain.search.SearchManager;
/**
* @author zhourui
* 2007-1-28
*/
public class SearchController extends HttpServlet {
private static final long serialVersionUID=1L;
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
// TODO Auto-generated method stub
String searchWord=arg0.getParameter("searchWord");
SearchManager searchManager=new SearchManager(searchWord);
List searchResult=null;
searchResult=searchManager.search();
RequestDispatcher dispatcher=arg0.getRequestDispatcher("search.jsp");
arg0.setAttribute("searchResult",searchResult);
dispatcher.forward(arg0, arg1);
}
}
随着pȝ信息的越来越多,怎么样从q些信息h中捞赯己想要的那一栚w变得非帔R要了Q全文检索是通常用于解决此类问题的方案,而Lucene则ؓ实现全文索的工具QQ何应用都可通过嵌入它来实现全文索?/p>
? 环境搭徏
从lucene.apache.org上下载最新版本的lucene.jarQ将此jar作ؓ目的build pathQ那么在目中就可以直接使用lucene了?/p>
? 使用说明
3.1. 基本概念
q里介绍的主要ؓ在用中l常到一些概念,以大安比较熟悉的数据库来进行类比的讲解Q用Luceneq行全文索的q程有点cM数据库的q个q程Qtable---à查询相应的字D|查询条g----àq回相应的记录,首先是IndexWriterQ通过它徏立相应的索引表,相当于数据库中的tableQ在构徏此烦引表旉指定的ؓ该烦引表采用何种方式q行构徏Q也是说对于其中的记录的字D以什么方式来q行格式的划分,q个在Lucene中称为AnalyzerQLucene提供了几U环境下使用的AnalyzerQSimpleAnalyzer、StandardAnalyzer、GermanAnalyzer{,其中StandardAnalyzer是经怋用的Q因为它提供了对于中文的支持Q在表徏好后我们需要往里面插入用于索引的记录,在Lucene中这个称为DocumentQ有点类似数据库中table的一行记录,记录中的字段的添加方法,在Lucene中称为FieldQ这个和数据库中基本一P对于Field Lucene分ؓ可被索引的,可切分的Q不可被切分的,不可被烦引的几种l合cdQ通过q几个元素基本上可以徏立v索引了。在查询时经常碰到的为另外几个概念,首先是QueryQLucene提供了几U经常可以用到的QueryQTermQuery、MultiTermQuery、BooleanQuery、WildcardQuery、PhraseQuery、PrefixQuery、PhrasePrefixQuery、FuzzyQuery、RangeQuery、SpanQueryQQuery其实也就是指对于需要查询的字段采用什么样的方式进行查询,如模p查询、语义查询、短语查询、范围查询、组合查询等Q还有就是QueryParserQQueryParser可用于创Z同的QueryQ还有一个MultiFieldQueryParser支持对于多个字段q行同一关键字的查询QIndexSearcher概念指的为需要对何目录下的烦引文件进行何U方式的分析的查询,有点象对数据库的哪种索引表进行查询ƈ按一定方式进行记录中字段的分解查询的概念Q通过IndexSearcher以及Query卛_查询出需要的l果QLuceneq回的ؓHits.通过遍历Hits可获取返回的l果的DocumentQ通过Document则可获取Field中的相关信息了?br />
比较一下Lucene和数据库Q?/p>
Lucene | 数据?/td> |
索引数据源:doc(field1,field2...) doc(field1,field2...) |
索引数据源:record(field1,field2...) record(field1..) |
DocumentQ一个需要进行烦引的“单元” 一个Document由多个字D늻?/td> | RecordQ记录,包含多个字段 |
FieldQ字D?/td> | FieldQ字D?/td> |
HitsQ查询结果集Q由匚w的Documentl成 | RecordSetQ查询结果集Q由多个Recordl成 |
需要熟悉几个接口:
分析器Analyzer
分析器主要工作是{选,一D|档进来以后,l过它,出去的时候只剩下那些有用的部分,其他则剔除。而这个分析器也可以自己根据需要而编写?br /> org.apache.lucene.analysis.AnalyzerQ这是一个虚构类Q以下两个借口均承它而来?/span>
org.apache.lucene.analysis.SimpleAnalyzerQ分析器Q支持最单拉丁语a?br /> org.apache.lucene.analysis.standard.StandardAnalyzerQ标准分析器Q除了拉丁语aq支持亚z语aQƈ在一些匹配功能上q行完善。在q个接口中还有一个很重要的构造函敎ͼStandardAnalyzer(String[] stopWords)Q可以对分析器定义一些用词语,q不仅可以免除检索一些无用信息,而且q可以在索中定义止的政L、非法性的索关键词?/span>
IndexWriter
IndexWriter的构造函数有三种接口Q针对目录Directory、文件File、文件\径String三种情况?br />例如IndexWriter(String path, Analyzer a, boolean create)Qpath为文件\径,a为分析器Qcreate标志是否重徏索引QtrueQ徏立或者覆盖已存在的烦引,falseQ扩展已存在的烦引。)
一些重要的ҎQ?/span>
接口??xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?> | 备注 |
addDocument(Document doc) | 索引d一个文?o:p> |
addIndexes(Directory[] dirs) | 目录中已存在烦引添加到q个索引 |
addIndexes(IndexReader[] readers) | 提供的索引d到这个烦?o:p> |
optimize() | 合ƈ索引q优?o:p> |
close() | 关闭 |
接口?o:p> | 备注 |
add(Field field) | d一个字D(FieldQ到Document?o:p> |
String get(String name) | 从文档中获得一个字D对应的文本 |
Field getField(String name) | 由字D名获得字段?o:p> |
Field[] getFields(String name) | 由字D名获得字段值的?o:p> |
Name | Stored | Indexed | Tokenized | use |
Keyword(String name, String value) | Y | Y | N | date,url |
Text(String name, Reader value) | N | Y | Y | short text fields: title,subject |
Text(String name, String value) | Y | Y | Y | longer text fields, like “body” |
UnIndexed(String name, String value) | Y | N | N | |
UnStored(String name, String value) | N | Y | Y | |
接口?o:p> | 备注 |
Doc(int n) | q回Wn个的文档的所有字D?o:p> |
length() | q回q个集中的可用个?o:p> |
索引建立部分的代码:
private void createIndex(String indexFilePath) throws Exception{
IndexWriter iwriter=getWriter(indexFilePath);
Document doc=new Document();
doc.add(Field.Keyword("name","jerry"));
doc.add(Field.Text("sender","bluedavy@gmail.com"));
doc.add(Field.Text("receiver","google@gmail.com"));
doc.add(Field.Text("title","用于索引的标?));
doc.add(Field.UnIndexed("content","不徏立烦引的内容"));
Document doc2=new Document();
doc2.add(Field.Keyword("name","jerry.lin"));
doc2.add(Field.Text("sender","bluedavy@hotmail.com"));
doc2.add(Field.Text("receiver","msn@hotmail.com"));
doc2.add(Field.Text("title","用于索引的第二个标题"));
doc2.add(Field.Text("content","建立索引的内?));
iwriter.addDocument(doc);
iwriter.addDocument(doc2);
iwriter.optimize();
iwriter.close();
}
private IndexWriter getWriter(String indexFilePath) throws Exception{
boolean append=true;
File file=new File(indexFilePath+File.separator+"segments");
if(file.exists())
append=false;
return new IndexWriter(indexFilePath,analyzer,append);
}
3.2.1. 对于某字D늚关键字的模糊查询
Query query=new WildcardQuery(new Term("sender","*davy*"));
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
3.2.2. 对于某字D늚关键字的语义查询
Query query=QueryParser.parse("索引","title",analyzer);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
3.2.3. 对于多字D늚关键字的查询
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(query);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
3.2.4. 复合查询(多种查询条g的综合查?
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);
Query mquery=new WildcardQuery(new Term("sender","bluedavy*"));
TermQuery tquery=new TermQuery(new Term("name","jerry"));
BooleanQuery bquery=new BooleanQuery();
bquery.add(query,true,false);
bquery.add(mquery,true,false);
bquery.add(tquery,true,false);
Searcher searcher=new IndexSearcher(indexFilePath);
Hits hits=searcher.search(bquery);
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i).get("name"));
}
? ȝ
怿大家通过上面的说明能知道Lucene的一个基本的使用ҎQ在全文索时大家先采用语义时的搜索,先搜索出有意义的内容Q之后再q行模糊之类的搜索,^_^Q这个还是需要根据搜索的需求才能定了,Luceneq提供了很多其他更好用的ҎQ这个就{待大家在用的q程中自己去q一步的摸烦了,比如对于Lucene本n提供的Query的更熟练的掌握,对于Filter、Sorter的用,自己扩展实现AnalyzerQ自己实现Query{等Q甚臛_以去了解一些关于搜索引擎的技?切词、烦引排?etc){等