Lucene 软件包的发布形式是一个 JAR 文件,下面我们分析一下这个 JAR 文件里面的主要的 JAVA 包,使读者对之有个初步的了解。
Package: org.apache.lucene.document
这个包提供了一些为封装要索引的文档所需要的类,比如 Document, Field。这样,每一个文档最终被封装成了一个 Document 对象。
Package: org.apache.lucene.analysis
这个包主要功能是对文档进行分词,因为文档在建立索引之前必须要进行分词,所以这个包的作用可以看成是为建立索引做准备工作。
Package: org.apache.lucene.index
这个包提供了一些类来协助创建索引以及对创建好的索引进行更新。这里面有两个基础的类:IndexWriter 和 IndexReader,其中 IndexWriter 是用来创建索引并添加文档到索引中的,IndexReader 是用来删除索引中的文档的。
Package: org.apache.lucene.search
这个包提供了对在建立好的索引上进行搜索所需要的类。比如 IndexSearcher 和 Hits, IndexSearcher 定义了在指定的索引上进行搜索的方法,Hits 用来保存搜索得到的结果。
===================================================================
一个简单的搜索应用程序
假设我们的电脑的目录中含有很多文本文档,我们需要查找哪些文档含有某个关键词。为了实现这种功能,我们首先利用 Lucene 对这个目录中的文档建立索引,然后在建立好的索引中搜索我们所要查找的文档。通过这个例子读者会对如何利用 Lucene 构建自己的搜索应用程序有个比较清楚的认识。
=======================================================================
建立索引
为了对文档进行索引,Lucene 提供了五个基础的类,他们分别是 Document, Field, IndexWriter, Analyzer, Directory。下面我们分别介绍一下这五个类的用途:
Document
Document 是用来描述文档的,这里的文档可以指一个 HTML 页面,一封电子邮件,或者是一个文本文件。一个 Document 对象由多个 Field 对象组成的。可以把一个 Document 对象想象成数据库中的一个记录,而每个 Field 对象就是记录的一个字段。
Field
Field 对象是用来描述一个文档的某个属性的,比如一封电子邮件的标题和内容可以用两个 Field 对象分别描述。
Analyzer
在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 Analyzer。Analyzer 把分词后的内容交给 IndexWriter 来建立索引。
IndexWriter
IndexWriter 是 Lucene 用来创建索引的一个核心的类,他的作用是把一个个的 Document 对象加到索引中来。
Directory
这个类代表了 Lucene 的索引的存储的位置,这是一个抽象类,它目前有两个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引的位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引的位置。
熟悉了建立索引所需要的这些类后,我们就开始对某个目录下面的文本文件建立索引了,清单1给出了对某个目录下的文本文件建立索引的源代码。
========================================================================
清单 1. 对文本文件建立索引
package TestLucene; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Date; 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; /** * This class demonstrate the process of creating index with Lucene * for text files */ public class TxtFileIndexer { public static void main(String[] args) throws Exception{ //indexDir 保存索引文件的路径 File indexDir = new File("D:\\luceneIndex"); //dataDir 需要加入索引的文件路径 File dataDir = new File("D:\\luceneData"); Analyzer luceneAnalyzer = new StandardAnalyzer(); File[] dataFiles = dataDir.listFiles(); IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); long startTime = new Date().getTime(); for(int i = 0; i < dataFiles.length; i++){ if(dataFiles[i].isFile() && dataFiles[i].getName().endsWith(".txt")){ System.out.println("Indexing file " + dataFiles[i].getCanonicalPath()); Document document = new Document(); Reader txtReader = new FileReader(dataFiles[i]); document.add(Field.Text("path",dataFiles[i].getCanonicalPath())); document.add(Field.Text("contents",txtReader)); indexWriter.addDocument(document); } } indexWriter.optimize(); indexWriter.close(); long endTime = new Date().getTime(); System.out.println("It takes " + (endTime - startTime) + " milliseconds to create index for the files in directory " + dataDir.getPath()); } }
在清单1中,我们注意到类 IndexWriter 的构造函数需要三个参数,
第一个参数指定了所创建的索引要存放的位置,他可以是一个 File 对象,也可以是一个 FSDirectory 对象或者 RAMDirectory 对象。
第二个参数指定了 Analyzer 类的一个实现,也就是指定这个索引是用哪个分词器对文挡内容进行分词。
第三个参数是一个布尔型的变量,如果为 true 的话就代表创建一个新的索引,为 false 的话就代表在原来索引的基础上进行操作。
接着程序遍历了目录下面的所有文本文档,并为每一个文本文档创建了一个 Document 对象。
然后把文本文档的两个属性:路径和内容加入到了两个 Field 对象中,接着在把这两个 Field 对象加入到 Document 对象中,
最后把这个文档用 IndexWriter 类的 add 方法加入到索引中去。
这样我们便完成了索引的创建。
接下来我们进入在建立好的索引上进行搜索的部分。
========================================================================
搜索文档
利用Lucene进行搜索就像建立索引一样也是非常方便的。在上面一部分中,我们已经为一个目录下的文本文档建立好了索引,现在我们就要在这个索引上进行搜索以找到包含某个关键词或短语的文档。Lucene提供了几个基础的类来完成这个过程,它们分别是呢IndexSearcher, Term, Query, TermQuery, Hits. 下面我们分别介绍这几个类的功能。
Query
这是一个抽象类,他有多个实现,比如TermQuery, BooleanQuery, PrefixQuery. 这个类的目的是把用户输入的查询字符串封装成Lucene能够识别的Query。
Term
Term 是搜索的基本单位,一个Term对象有两个String类型的域组成。生成一个Term对象可以有如下一条语句来完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一个参数代表了要在文档的哪一个Field上进行查找,第二个参数代表了要查询的关键词。
TermQuery
TermQuery 是抽象类Query的一个子类,它同时也是Lucene支持的最为基本的一个查询类。生成一个TermQuery对象由如下语句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的构造函数只接受一个参数,那就是一个Term对象。
IndexSearcher
IndexSearcher是用来在建立好的索引上进行搜索的。它只能以只读的方式打开一个索引,所以可以有多个IndexSearcher的实例在一个索引上进行操作。
Hits
Hits是用来保存搜索的结果的。
介绍完这些搜索所必须的类之后,我们就开始在之前所建立的索引上进行搜索了,清单2给出了完成搜索功能所需要的代码。
清单2 :在建立好的索引上进行搜索
package TestLucene; import java.io.File; import org.apache.lucene.document.Document; import org.apache.lucene.index.Term; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.FSDirectory; /** * This class is used to demonstrate the * process of searching on an existing * Lucene index * */ public class TxtFileSearcher { public static void main(String[] args) throws Exception{ String queryStr = "lucene"; //This is the directory that hosts the Lucene index File indexDir = new File("D:\\luceneIndex"); FSDirectory directory = FSDirectory.getDirectory(indexDir,false); IndexSearcher searcher = new IndexSearcher(directory); if(!indexDir.exists()){ System.out.println("The Lucene index is not exist"); return; } Term term = new Term("contents",queryStr.toLowerCase()); TermQuery luceneQuery = new TermQuery(term); Hits hits = searcher.search(luceneQuery); for(int i = 0; i < hits.length(); i++){ Document document = hits.doc(i); System.out.println("File: " + document.get("path")); } } }
在清单2中,类IndexSearcher的构造函数接受一个类型为Directory的对象,Directory是一个抽象类,它目前有两个子类: FSDirctory和RAMDirectory. 我们的程序中传入了一个FSDirctory对象作为其参数,代表了一个存储在磁盘上的索引的位置。构造函数执行完成后,代表了这个 IndexSearcher以只读的方式打开了一个索引。然后我们程序构造了一个Term对象,通过这个Term对象,我们指定了要在文档的内容中搜索包含关键词”lucene”的文档。接着利用这个Term对象构造出TermQuery对象并把这个TermQuery对象传入到 IndexSearcher的search方法中进行查询,返回的结果保存在Hits对象中。最后我们用了一个循环语句把搜索到的文档的路径都打印了出来。好了,我们的搜索应用程序已经开发完毕,怎么样,利用Lucene开发搜索应用程序是不是很简单。
[java语言]解决Word文档的检索问题,lucene我的天职是搜索
原创空间, 软件技术
邢红瑞 发表于 2005-11-20 13:08:37
lunece是个姓氏,Lucene is Doug’s wife’s middle name; it’s also her maternal grandmother’s first name.
看了车东老大的blog,针对MSWord文档的解析器,因为Word文档和基于ASCII的RTF文档不同,
需要使用COM对象机制解析。其实apache的POI完全可以做到解析MSWord文档。
我修改了别人的一个例子,算是抛砖引玉,大家不要那转头打我。
Lucene并没有规定数据源的格式,而只提供了一个通用的结构(Document对象)来接受索引的输入,
但好像只能是文本数据。
package org.tatan.framework; import java.io.PrintStream; import java.io.PrintWriter; public class DocumentHandlerException extends Exception { private Throwable cause; /** * Default constructor. */ public DocumentHandlerException() { super(); } /** * Constructs with message. */ public DocumentHandlerException(String message) { super(message); } /** * Constructs with chained exception. */ public DocumentHandlerException(Throwable cause) { super(cause.toString()); this.cause = cause; } /** * Constructs with message and exception. */ public DocumentHandlerException(String message, Throwable cause) { super(message, cause); } /** * Retrieves nested exception. */ public Throwable getException() { return cause; } public void printStackTrace() { printStackTrace(System.err); } public void printStackTrace(PrintStream ps) { synchronized (ps) { super.printStackTrace(ps); if (cause != null) { ps.println("--- Nested Exception ---"); cause.printStackTrace(ps); } } } public void printStackTrace(PrintWriter pw) { synchronized (pw) { super.printStackTrace(pw); if (cause != null) { pw.println("--- Nested Exception ---"); cause.printStackTrace(pw); } } } }
解析MSWORD的类
package org.tatan.framework; import org.apache.poi.hdf.extractor.WordDocument; import java.io.InputStream; import java.io.StringWriter; import java.io.PrintWriter; public class POIWordDocHandler { public String getDocument(InputStream is) throws DocumentHandlerException { String bodyText = null; try { WordDocument wd = new WordDocument(is); StringWriter docTextWriter = new StringWriter(); wd.writeAllText(new PrintWriter(docTextWriter)); docTextWriter.close(); bodyText = docTextWriter.toString(); } catch (Exception e) { throw new DocumentHandlerException( "Cannot extract text from a Word document", e); } if ((bodyText != null) && (bodyText.trim().length() > 0)) { return bodyText; } return null; } }
建立索引的类
package org.tatan.framework; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Date; public class Indexer { public static void main(String[] args) throws Exception { File indexDir = new File("d:/testdoc/index"); File dataDir = new File("d:/testdoc/msword"); long start = new Date().getTime(); int numIndexed = index(indexDir, dataDir); long end = new Date().getTime(); System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds"); } public static int index(File indexDir, File dataDir) throws Exception { if (!dataDir.exists() || !dataDir.isDirectory()) { throw new IOException(dataDir + " does not exist or is not a directory"); } IndexWriter writer = new IndexWriter(indexDir, new CJKAnalyzer(), true) writer.setUseCompoundFile(false); indexDirectory(writer, dataDir); int numIndexed = writer.docCount(); writer.optimize(); writer.close(); return numIndexed; } private static void indexDirectory(IndexWriter writer, File dir) throws Exception { File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { File f = files[i]; if (f.isDirectory()) { indexDirectory(writer, f); // recurse } else if (f.getName().endsWith(".doc")) { indexFile(writer, f); } } } private static void indexFile(IndexWriter writer, File f) throws Exception { if (f.isHidden() || !f.exists() || !f.canRead()) { return; } System.out.println("Indexing " + f.getCanonicalPath()); Document doc = new Document(); POIWordDocHandler handler = new POIWordDocHandler(); doc.add(Field.UnStored("body", handler.getDocument(new FileInputStream(f)))); doc.add(Field.Keyword("filename", f.getCanonicalPath())); writer.addDocument(doc); } }
要注意的问题:使用Field对象UnStored函数,只全文索引,不存储。
检索的类
package org.tatan.framework; import org.apache.lucene.document.Document; 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 org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.analysis.Token; import org.apache.lucene.analysis.cjk.CJKAnalyzer; public class Searcher { public static void main(String[] args) throws Exception { Directory fsDir = FSDirectory.getDirectory("D:\\testdoc\\index", false); IndexSearcher is = new IndexSearcher(fsDir); Token[] tokens = AnalyzerUtils.tokensFromAnalysis(new CJKAnalyzer(), "一人一情"); for (int i = 0; i < tokens.length; i++) { Query query = QueryParser.parse(tokens[i].termText(), "body", new CJKAnalyzer()); Hits hits = is.search(query); for (int j = 0; j < hits.length(); j++) { Document doc = hits.doc(j); System.out.println(doc.get("filename")); } } } }
要注意的问题:不要使用TermQuery检索不出中文,目前还有中文切词功能。