下载与安装
首先,你需要到Lucene的官方网站http://jakarta.apache.org/lucene/ 去下载一份拷贝,最新版是1.4。下载后将得到一个名为lucene-1.4-final.zip的压缩文件,将其解压,里面有一个名为lucene-1.4-final.jar的文件,这就是Lucene组件包了,若需要在项目使用Lucene,只需要把lucene-1.4-final.jar置于类路径下即可,至于解压后的其他文件都是参考用的。
接下来,我用Eclipse建立一个工程,实现基于Lucene的建库、记录加载和记录查询等功能。
如上图所示,这是开发完成后的工程,其中有三个源文件CreateDataBase.java,InsertRecords.java,QueryRecords.java,分别实现建库、入库、检索的功能。
以下是对这三个源文件的分析。
建库源码及说明
CreateDataBase.java |
packagecom.holen.part1;
importjava.io.File; importorg.apache.lucene.analysis.standard.StandardAnalyzer; importorg.apache.lucene.index.IndexWriter;
/** * @authorHolenChen *初始化检索库 */ public classCreateDataBase{
publicCreateDataBase(){ }
public intcreateDataBase(Filefile){ intreturnValue=0; if(!file.isDirectory()){ file.mkdirs(); } try{ IndexWriterindexWriter= newIndexWriter(file,newStandardAnalyzer(),true); indexWriter.close(); returnValue=1; }catch(Exceptionex){ ex.printStackTrace(); } returnreturnValue; }
/** *传入检索库路径,初始化库 * @paramfile * @return */ public intcreateDataBase(Stringfile){ return this.createDataBase(newFile(file)); }
public static voidmain(String[]args){ CreateDataBasetemp= newCreateDataBase(); if(temp.createDataBase("e:\\lucene\\holendb")==1){ System.out.println("db init succ"); } } }
|
说明:这里最关键的语句是IndexWriterindexWriter= newIndexWriter(file,newStandardAnalyzer(),true)。
第一个参数是库的路径,也就是说你准备把全文检索库保存在哪个位置,比如main方法中设定的“e:\\lucene\\holendb”,Lucene支持多库,且每个库的位置允许不同。
第二个参数是分析器,这里采用的是Lucene自带的标准分析器,分析器用于对整篇文章进行分词解析,这里的标准分析器实现对英文(或拉丁文,凡是由字母组成,由空格分开的文字均可)的分词,分析器将把整篇英文按空格切成一个个的单词(在全文检索里这叫切词,切词是全文检索的核心技术之一,Lucene默认只能切英文或其他拉丁文,默认不支持中日韩等双字节文字,关于中文切词技术将在后续章节重点探讨)。
第三个参数是是否初始化库,这里我设的是true,true意味着新建库或覆盖已经存在的库,false意味着追加到已经存在的库。这里新建库,所以肯定需要初始化,初始化后,库目录下只存在一个名为segments的文件,大小为1k。但是当库中存在记录时执行初始化,库中内容将全部丢失,库回复到初始状态,即相当于新建了该库,所以真正做项目时,该方法一定要慎用。
加载记录源码及说明
InsertRecords.java |
packagecom.holen.part1;
importjava.io.File; importjava.io.FileReader; importjava.io.Reader; importorg.apache.lucene.analysis.standard.StandardAnalyzer; importorg.apache.lucene.document.Document; importorg.apache.lucene.document.Field; importorg.apache.lucene.index.IndexWriter;
/** * @authorHolenChen *记录加载 */ public classInsertRecords{
publicInsertRecords(){ }
public intinsertRecords(Stringdbpath,Filefile){ intreturnValue=0; try{ IndexWriterindexWriter = newIndexWriter(dbpath,newStandardAnalyzer(),false); this.addFiles(indexWriter,file); returnValue=1; }catch(Exceptionex){ ex.printStackTrace(); } returnreturnValue; }
/** *传入需加载的文件名 * @paramfile * @return */ public intinsertRecords(Stringdbpath,Stringfile){ return this.insertRecords(dbpath,newFile(file)); }
public voidaddFiles(IndexWriterindexWriter,Filefile){ Documentdoc= newDocument(); try{ doc.add(Field.Keyword("filename",file.getName()));
//以下两句只能取一句,前者是索引不存储,后者是索引且存储 //doc.add(Field.Text("content",new FileReader(file))); doc.add(Field.Text("content",this.chgFileToString(file)));
indexWriter.addDocument(doc); indexWriter.close(); }catch(Exceptionex){ ex.printStackTrace(); } }
/** *从文本文件中读取内容 * @paramfile * @return */ publicStringchgFileToString(Filefile){ StringreturnValue= null; StringBuffersb= newStringBuffer(); char[]c= new char[4096]; try{ Readerreader= newFileReader(file); intn=0; while(true){ n=reader.read(c); if(n>0){ sb.append(c,0,n); }else{ break; } } reader.close(); }catch(Exceptionex){ ex.printStackTrace(); } returnValue=sb.toString(); returnreturnValue; }
public static voidmain(String[]args){ InsertRecordstemp= newInsertRecords(); Stringdbpath="e:\\lucene\\holendb"; //holen1.txt中包含关键字"holen"和"java" if(temp.insertRecords(dbpath,"e:\\lucene\\holen1.txt")==1){ System.out.println("add file1 succ"); } //holen2.txt中包含关键字"holen"和"chen" if(temp.insertRecords(dbpath,"e:\\lucene\\holen2.txt")==1){ System.out.println("add file2 succ"); } } }
|
说明:这个类里面主要有3个方法insertRecords(Stringdbpath,Filefile),addFiles(IndexWriterindexWriter,Filefile),chgFileToString(Filefile)。
ChgFileToString方法用于读取文本型文件到一个String变量中。
InsertRecords方法用于加载一条记录,这里是将单个文件入全文检索库,第一个参数是库路径,第二个参数是需要入库的文件。
InsertRecords需要调用addFiles,addFiles是文件入库的真正执行者。AddFiles里有如下几行重点代码:
doc.add(Field.Keyword("filename",file.getName()));
注意,在Lucene里没有严格意义上表,Lucene的表是通过Field类的方法动态构建的,比如Field.Keyword("filename",file.getName())就相当于在一条记录加了一个字段,字段名为filename,该字段的内容为file.getName()。
常用的Field方法如下:
方法 |
切词 |
索引 |
存储 |
用途 |
Field.Text(String name, String value) |
Y |
Y |
Y |
标题,文章内容 |
Field.Text(String name, Reader value) |
Y |
Y |
N |
META信息 |
Field.Keyword(String name, String value) |
N |
Y |
Y |
作者 |
Field.UnIndexed(String name, String value) |
N |
N |
Y |
文件路径 |
Field.UnStored(String name, String value) |
Y |
Y |
N |
与第二种类似 |
为了更深入的了解全文检索库,我们可以将全文检索库与通常的关系型数据库(如Oracle,Mysql)作一下对比。
全文检索库对关系型数据库对比 |
||
对比项 |
全文检索库(Lucene) |
关系型数据库(Oracle) |
核心功能 |
以文本检索为主,插入(insert)、删除(delete)、修改(update)比较麻烦,适合于大文本块的查询。 |
插入(insert)、删除(delete)、修改(update)十分方便,有专门的SQL命令,但对于大文本块(如CLOB)类型的检索效率低下。 |
库 |
与Oracle类似,都可以建多个库,且各个库的存储位置可以不同。 |
可以建多个库,每个库一般都有控制文件和数据文件等,比较复杂。 |
表 |
没有严格的表的概念,比如Lucene的表只是由入库时的定义字段松散组成。 |
有严格的表结构,有主键,有字段类型等。 |
记录 |
由于没有严格表的概念,所以记录体现为一个对象,在Lucene里记录对应的类是Document。 |
Record,与表结构对应。 |
字段 |
字段类型只有文本和日期两种,字段一般不支持运算,更无函数功能。 在Lucene里字段的类是Field,如document(field1,field2…) |
字段类型丰富,功能强大。 record(field1,field2…) |
查询结果集 |
在Lucene里表示查询结果集的类是Hits,如hits(doc1,doc2,doc3…) |
在JDBC为例, Resultset(record1,record2,record3...) |
两种库对比图如下:
检索源码及说明
QueryRecords.java |
packagecom.holen.part1;
importjava.util.ArrayList; importorg.apache.lucene.analysis.standard.StandardAnalyzer; importorg.apache.lucene.document.Document; importorg.apache.lucene.queryParser.QueryParser; importorg.apache.lucene.search.Hits; importorg.apache.lucene.search.IndexSearcher; importorg.apache.lucene.search.Query; importorg.apache.lucene.search.Searcher;
/** * @authorHolenChen *检索查询 */ public classQueryRecords{
publicQueryRecords(){ }
/** *检索查询,将结果集返回 * @paramsearchkey * @paramdbpath * @paramsearchfield * @return */ publicArrayListqueryRecords(Stringsearchkey,Stringdbpath,Stringsearchfield){ ArrayListlist= null; try{ Searchersearcher= newIndexSearcher(dbpath); Queryquery =QueryParser.parse(searchkey,searchfield,newStandardAnalyzer()); Hitshits=searcher.search(query); if(hits!= null){ list= newArrayList(); inttemp_hitslength=hits.length(); Documentdoc= null; for(inti=0;i<temp_hitslength;i++){ doc=hits.doc(i); list.add(doc.get("filename")); } } }catch(Exceptionex){ ex.printStackTrace(); } returnlist; }
public static voidmain(String[]args){ QueryRecordstemp= newQueryRecords(); ArrayListlist= null; list=temp.queryRecords("holen","e:\\lucene\\holendb","content"); for(inti=0;i<list.size();i++){ System.out.println((String)list.get(i)); } } }
|
说明:该类中Searcher负责查询,并把查询结果以Hits对象集方式返回,Hits好比JDBC中的RecordSet,Hits是Document的集合,每个Document相当于一条记录,Document中包含一个或多个字段,可以通过Document.get(“字段名”)方法得到每个字段的内容。
通过这三个类,就完成了一个简单的基于Lucene的全文检索应用。
4.总结
Lucene十分精练纯粹,就一个jar包,引入到你的工程中,调用其接口,就可以为你的应用增添全文检索功能。
通过上一节的初步应用会发现,Lucene使用起来很简单,与JDBC有些类似,应用时重点掌握好IndexWriter,Document,Field,Searcher等几个类即可。
Lucene的结构很清晰,每个package司职一项,比如org.apache.Lucene.search负责检索,org.apache.Lucene.index索引,org.apache.Lucene.analysis切词等,且Lucene的主要动作都采用了抽象类,扩展起来十分方便。
相对于一些商业化全文检索,Lucene的入库速度更快。因为它的存储采取分步合并的方法,先建立小索引,待时机成熟才把小索引合并到大索引树上。因此,我们在操作应用数据时可以同步进行全文检索库的操作而不会(或许很少)影响系统的效能。
Lucene性能稳定,使用简单,而且开源免费,有Apache基金在后面做支撑,资金和技术力量都十分雄厚,这两年也一直是稳步更新,每次新版本的推出,业界均争相报导。
参考资料
1. Introduction to Text Indexing with Apache Jakarta Lucene(Otis Gospodnetic)
2. Lucene Introduction in Chinese(车东)
3. Lucene Tutorial(Steven J. Owens)
作者简介
陈光- J2EE项目经理,熟悉EJB、XML,致力于Aapche Jakarta项目的应用与推广,可通过[email protected]与作者联系。