主页 > IT > Lucene学习系列(一)

Lucene学习系列(一)

归类于 IT

回首从事技术工作至今的经历,发现虽然学习了很多东西,可是没有记录学习过程,很多东西时间一长都快忘光了。技术需要积累,而积累在乎细节。很多软件产品,解决方案就和大道理一样,并不难从总体上掌握,但是现实中,会碰到很多细节性的问题,是否真正掌握一个产品或者解决方案,往往体现在能否正确合理的解决细节问题。所以,对工作学习过程中碰到的各种技术细节进行记录是非常有必要的。

最近开始学习Lucene,看的书为Lucene in Action第二版,准备坚持写一个系列的学习笔记,也算对这段学习的记录。

当今是信息爆炸的时代。海量的信息中,如何有效的提取出我们感兴趣的信息呢?这个问题是信息检索的研究领域,信息检索(Information Retrieve),即IR。Lucene是一个高可用,可扩展的信息检索库。利用Lucene,可以为应用添加搜索功能。Lucene是一个Java库,它提供了简单但强有力的核心API,实现全文检索。Lucene不是一个应用程序,不提供类似google等web搜索引擎的完整功能。它专注于为文档集合建立索引并执行查询搜索。

一个搜索应用有两大部分组成:建立索引和对索引进行查询。类似于从字典中查字,索引的使用可以加快找到目标文档。索引中记录了很多信息,如某个关键词出现在哪些文档中,及分别出现的次数,文档的绝对路径等。建立索引和对索引进行查询由子步骤组成。下面分别进行介绍,注意其中颜色为红色的子步骤由Lucene实现。

1) 建立索引

1. 获取数据内容,可能来自文件系统中的文件,数据库等

2. 建立document,一个document代表了一个数据内容单元,如一个文件。一个document由多个field组成,如title,body,abstract,url等field。document中的field都是key-value对结构。

3. 分析document,不能直接对文档进行索引,通常先将文档切割为更小的原子元素,叫做token,对英文来说可能是一个单词。这里有些情况是十分复杂的,如对单词的各种时态,名词单复数等,是否认为是一样的?而中文的切分又是一个需要特别处理的难题。将内容切割后,就可以建立索引了。

4. 索引document,根据切割了内容的document,建立可用于快速查询的索引。

2) 对索引进行查询

1. 查询用户界面,供用户输入查询条件

2. 建立Query,将用户的查询请求转换为Query对象。Query对象可以用Lucene提供的QueryParser对用户输入进行转换而得到。

3. 在索引上执行Query,该步是查询的关键,Lucene做了很多工作。该步也可扩展,可以定制如何收集,过滤和排序结果。有三种查询的理论模型:纯Boolean模型,向量空间模型和概率模型。
纯Boolean模型:document或者匹配,或者不匹配query,没有计算得分,且匹配的document是无序的。
向量空间模型:document和query被建模为向量,每个唯一的term是一维,document和query之间的相关性或相似性由向量距离度量。
概率模型:使用完全概率方法计算一个document是好匹配的概率。

4. 渲染结果,将查询结果呈现给用户。

从上面可以看到,Lucene两大块主要功能:文本内容经切割后索引入库和根据查询条件查询索引并返回匹配的结果。

下面给出一个简单的例子,可以从中看到Lucene API的使用是很简单的。

Indexer类:对某个目录中的所有txt文件进行索引


public class Indexer
{
public static void main(String[] args) throws Exception{
if(args.length!=2){
throw new IllegalArgumentException(“Usage: java”+Indexer.class.getName()+” “);
}
String indexDir=args[0];
String dataDir=args[1];
long start=System.currentTimeMillis();
Indexer indexer=new Indexer(indexDir);
int numIndexed;
try{
numIndexed=indexer.index(dataDir,new TextFilesFilter());
}finally{
indexer.close();
}
long end=System.currentTimeMillis();
System.out.println(“Indexing “+numIndexed+” files took “+(end-start)+” milliseconds”);
}

private IndexWriter writer;
public Indexer(String indexDir) throws IOException{
Directory dir=FSDirectory.open(new File(indexDir));
writer=new IndexWriter(dir,new StandardAnalyzer(Version.LUCENE_30),true,IndexWriter.MaxFieldLength.UNLIMITED);
}

public void close() throws IOException{
writer.close();
}

public int index(String dataDir,FileFilter filter) throws Exception{
File[] files=new File(dataDir).listFiles();
for(File f:files){
if(!f.isDirectory()&&!f.isHidden()&&f.exists()&&f.canRead()&&(filter==null || filter.accept(f))){
indexFile(f);
}
}
return writer.numDocs();
}

private static class TextFilesFilter implements FileFilter
{
public boolean accept(File path){
return path.getName().toLowerCase().endsWith(“.txt”);
}
}

protected Document getDocument(File f) throws Exception{
Document doc=new Document();
doc.add(new Field(“content”,new FileReader(f)));
doc.add(new Field(“filename”,f.getName(),Field.Store.YES,Field.Index.NOT_ANALYZED));
doc.add(new Field(“fullpath”,f.getCanonicalPath(),Field.Store.YES,Field.Index.NOT_ANALYZED));
return doc;
}

private void indexFile(File f) throws Exception{
System.out.println(“Indexing “+f.getCanonicalPath());
Document doc=getDocument(f);
writer.addDocument(doc);
}
}

Searcher类:查询索引的文件中,content field包含指定关键词的文档的绝对路径


public class Searcher
{
public static void main(String[] args) throws Exception{
if(args.length!=2){
throw new IllegalArgumentException(“Usage: java”+Searcher.class.getName()+” “);
}
String indexDir=args[0];
String q=args[1];
search(indexDir,q);
}

public static void search(String indexDir,String q) throws IOException,ParseException{
Directory dir=FSDirectory.open(new File(indexDir));
IndexSearcher is=new IndexSearcer(dir);
QueryParser parser=new QueryParser(Version.LUCENE_30,”content”,new StandardAnalyzer(Version.LUCENE_30));
Query query=parser.parse(q);
long start=System.currentTimeMillis();
TopDocs hits=is.search(query,10);
long end=System.currentTimeMillis();

System.out.println(“Found “+hits.totalHits+” document(s) (in “+(end-start)+” milliseconds) that matched query ‘”+q+”‘:”);
for(ScoreDoc scoreDoc:hits.scoreDocs){
Document doc=is.doc(scoreDoc.doc);
System.out.println(doc.get(“fullpath”));
}
is.close();
}
}

从上述代码中可以看到,Lucene的API的使用还是非常简单的

归类于 IT

评论已经关闭

顶部