以前听过Lucene的大名,但是实际项目中一直没机会用到。直到今天,无事看看,发现者东西真是厉害,很多知名公司都已经在用了,包括google和apple。
这篇文章将分3部分介绍lucene, 1. Lucene简单的介绍 2.如何创建索引 3.如何搜索
1. Lucene简单的介绍
Lucene是一个全文检索引擎,官网是http://lucene.apache.org 当前最新版是4.4
官网除了介绍Lucene外,还在显目位置放了Solr的东西,看介绍说是基于Lucene core的高性能搜索服务器,日后有空再去研究他。
后来又去弄了本Lucene in action 2nd, 准备走走经典流程Hello World。
和预想的一样,搜索引擎,逃脱不了这样的套路:
创建索引的时候
用web爬虫等数据内容获取工具拿到数据 接着 将内容进行分词处理,留下有意义的词 然后创建索引保存起来
在搜索的时候
得到用户的搜索词,解析分词,找到索引文件,搜索,返回搜索结果。
Lucene in action中是这样描述的:
大体是相同的,从资料中获取数据,构造文档,分析文档进行分词,然后生成索引文档,然后保存。当搜索的时候,用户通过搜索UI输入关键字,应用程序创建搜索对象,搜索索引,然后返回结果。
接下来,我们先走一遍Hello World,再介绍下各个重要部分。
2. 创建索引
下面是创建索引的代码:
/** * */ package demo; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; /** * @author * */ publicclass Indexer { private IndexWriter writer; /** * * @param indexDir * @throws IOException */ public Indexer(String indexDir) throws IOException { // 定义索引的存放目录为File System Directory dir = FSDirectory.open(new File(indexDir)); // 配置创建创建方式,使用标准的Analyzer IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_44, new StandardAnalyzer(Version.LUCENE_44)); writer = new IndexWriter(dir, conf); } publicint 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))) { // 得到符合条件的txt文件 indexFile(f); } } return writer.numDocs(); } privatevoid indexFile(File f) throws Exception { System.out.println("Indexing " + f.getCanonicalPath()); Document doc = getDocument(f); writer.addDocument(doc); } // 为Document 添加 Field protected Document getDocument(File f) throws Exception { Document doc = new Document(); doc.add(new TextField("content", new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")))); doc.add(new StringField("filename", f.getName(), Field.Store.YES)); doc.add(new StringField("fullpath", f.getCanonicalPath(), Field.Store.YES)); return doc; } /** * @throws IOException */ publicvoid close() throws IOException { writer.close(); } // 过滤文件 privatestaticclass TextFilesFilter implements FileFilter { publicboolean accept(File path) { return path.getName().toLowerCase().endsWith(".txt"); } } /** * @param args * @throws Exception */ publicstaticvoid main(String[] args) throws Exception { // 索引存放路径 String indexDir = "/Users/apple/Documents/index"; // 数据文件路径 String dataDir = "/Users/apple/Documents/data"; long start = System.currentTimeMillis(); Indexer indexer = new Indexer(indexDir); int numIndexed; try { // 调用自己写得index方法创建索引 numIndexed = indexer.index(dataDir, new TextFilesFilter()); } finally { indexer.close(); } long end = System.currentTimeMillis(); System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds"); } } |
这段代码是这样:
首先定义了数据存放目录,以及索引目录,后者空,前者包含几个txt文档,文档里面有几句英文句子(当然支持中文,但代码会复杂,不在hello world范畴了)
定义了2个目录后,接下来就是读取文档,获取内容了,这一步大家可以按照自己的方法写,只要能够遍历txt文档,并取得内容即可。
接着,
这2个方法是把File对象创建成Document,也就是把内容存入到Document中,然后添加到IndexWriter中。一个Document对象包含若干个Field。你可以这么理解:一个Document对象想象成数据库表中得一行,而Field就是某列。
说到IndexWriter,她是这样创建的:
StandardAnalyzer,按照字面理解是标准分词解析器,按照我的猜想,如果要解析中文,估计也是在这里动手脚。她帮助IndexWriter解析你传进来的Field的内容,进行分词,词加权等操作。IndexWriter负责将生成的索引写入到FSDirectory中。也可以是RAM,你可以修改38行的代码。
这样,一个索引的创建过程就完成了。如果你的代码报错,尤其是StandardAnalyzer找不到,你需要导入Analyzer / common包下的那个jar包。
运行一遍,即可在index目录下生成索引了:
3. 搜索
有了索引后,就是搜索了,搜索相对简单些,构造搜索对象,然后搜索并显示结果。
这个是她的运行结果
至于那个为什么是null,Lucene in action没说,按照我的猜测,因为她是一个TextField对象,从文本读取出来的,她可能很大很大,如果把这个内容也放入索引,那么索引库的体积将会非常大,所以默认是不保存TextField,至于我的猜想是否正确,需待深入了解。热心的网友也可给我留言。
就这样,一个Hello World就完成了。
总结:
创建索引的核心类:IndexWriter,Directory,Analyzer,Document,Field
IndexWriter是创建索引的核心组件,她负责创建一个新索引货打开已经存在的索引,以及添加,删除更新索引中得文档。她是负责写索引的,所以也要给他指定一个Directory,告诉她索引存哪。
Directory代表了Lucene 索引的存放地址,她又多种方式可用存,File System, RAM等
Analyzer,在文本被索引前,将会被Analyzer处理。做分词处理,去掉一些无意义的词,如空格,停顿,the,之类的词。有多种Analyzer,自由选择以解析富文本对象等。
Document,代表field的集合。可被认为是web page,email message,数据表某一行,而field就是元数据了,比如标题,内容,创建时间,路径等等,自由自定。不过你要记住的是,Lucene只能处理文本和数字。所以你要利用各种方法先将非文本转成文本。
Field就是Lucene将要索引的值
搜索的关键对象:
IndexSearcher,负责搜索INdexWriter写得索引。
Directory dir = FSDirectory.open(new File("/tmp/index"));
IndexSearcher searcher = new IndexSearcher(dir);
Query q = new TermQuery(new Term("contents", "lucene"));
TopDocs hits = searcher.search(q, 10);
searcher.close();
这是她的典型用法
Term,是搜索的基本单元,与Field对象类似。包含用于搜索的Field name和value
Query,搜索对象,和JDBC中得Query功能一样,Lucene有很多实现方式,例子中用到的是TermQuery
TermQuery基本的搜索方式
TopDoc,也就是搜索结果,她包含一个docID,你可以通过她取得document对象。