一直都想花时间学习一下lucene的,主要有两个原因没有这么做。一是因为版本更新太快,而且每个版本的变化都比较大。二是花的时间精力可能有点多。现在lucene3.0已经发布了,基本上已经比较稳定了,而且公司以后很可能也会用到lucene,所以就提前学习一下吧!
关于lucene3.0的资料,网上的确很少,所以研究学习起来,困难还是有的。
lucene主要做两件事,建立索引和查询索引。下面通过网上找到的一个例子做个简单的介绍。
public static int index(File indexDir, File dataDir) throws IOException {
if (!dataDir.exists() || !dataDir.isDirectory()) {
throw new IOException(dataDir
+ " does not exist or is not a directory");
}
Directory directory = FSDirectory.open(indexDir);
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
IndexWriter writer = new IndexWriter(directory, analyzer, true,
IndexWriter.MaxFieldLength.LIMITED);
indexDirectory(writer, dataDir);
int numIndexed = writer.numDocs();
writer.optimize();
writer.close();
return numIndexed;
}
这里需要解释几个概念:
Directory
这个类代表了 Lucene 的索引的存储的位置,这是一个抽象类,它目前有三个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引的位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引的位置。还有一个是FileSwitchDirectory,这个API先不要用,因为API文档注明过有可能在将来的版本进行修改。如果数据量不大,可以直接使用RAMDirectory,毕竟在内存中,数据的访问会更快。RAMDirectory还有一个地方比较常用,那就是单元测试,可以在初始化测试方法时创建索引,测试完成后删除索引,这样就不会留下不必要的垃圾文件。
Analyzer
在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 Analyzer,中文分词也就是在这里实现一个中文分词的解析器。Analyzer的处理对象必须是Document,
IndexWriter
IndexWriter 是 Lucene 用来创建索引的一个核心的类,他的作用是把一个个的 Document 对象拿给Analyzer处理。
private static void indexDirectory(IndexWriter writer, File dir)
throws IOException {
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(".txt")) {
indexFile(writer, f);
}
}
}
private static void indexFile(IndexWriter writer, File f)
throws IOException {
if (f.isHidden() || !f.exists() || !f.canRead()) {
return;
}
System.out.println("Indexing " + f.getCanonicalPath());
Document doc = new Document();
doc.add(new Field("contents", new FileReader(f)));
doc.add(new Field("filename", f.getCanonicalPath(), Field.Store.YES,
Field.Index.ANALYZED));
writer.addDocument(doc);
}
其中document是field的集合,field是term的集合。term是一个二元组,形式如<FieldName,Text>。
上面用到了Field.Index与Field.Store的字段,下面将对它们内部的每个字段都介绍一下:
Index.ANALYZED
通过分析器将输入文本分解成可独立查询的词汇单元,这对于正式的文本字段很有用,如body,title等。
Index.NOT_ANALYZED
对这个字段建立索引,但不对该内容进行分解,把整个字段的内容当做单个可查询的词汇单元。如URLS,filepath,dates,personal names,social security numbers,telephone number等。对于“精确匹配”查找也非常有用。
Index.ANALYZED_NO_NORMS
这是一个比较高级一些变量,表示不在索引中存储关于norms的信息。而norms是一个对索引查询进行加分的一个记录。
Index.NOT_ANALYZED_NO_NORMS
这个变量与上面的解释差不多,只是综合了一下。
Index.NO
不让字段的值在查询索引时可用。
Store.YES
存储字段,而且彻底存储的内容。这样IndexReader(IndexSearcher)查询索引时,就可以查询到字段的内容。这对于想显示查询结果内容的字段来说很有用,如URL,title,ID。一般不要存储非常大的字段。
Store.NO
与上面的字段相反,这个字段经常伴随着Index.ANALYZED一起使用,用于只需要查询结果而不需要查询内容的文本字段。
建立索引直接调用index方法即可,其中一个参数表示创建的索引存放的目录,另一个参数表示需要建立索引的存放txt文件的目录。
索引建立好以后,就可以查询了,查询的步骤相对来说,要简单一些:
public static void search(File indexDir, String q) throws Exception {
IndexSearcher is = new IndexSearcher(FSDirectory.open(indexDir),true);//read-only
String field = "contents";
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, field, new StandardAnalyzer(Version.LUCENE_CURRENT));
Query query = parser.parse(q);
//返回匹配的前两条记录
TopDocs topDocs = is.search(query, 2);
ScoreDoc[] hits = topDocs.scoreDocs;
for (int i = 0; i < hits.length; i++) {
Document doc = is.doc(hits[i].doc);//new method is.doc()
System.out.println(doc.getField("filename")+" "+hits[i].toString()+" ");
}
}
上面需要解释的是TopDocs类,它表示一个存放前N条查询记录的容器。ScoreDoc表示具体存放内容,有点领域模型的感觉。ScoreDoc其本身包含两种属性,一是document,另一种是score。上面方法的参数q表示需要查询的关键字。
这样,一个简单的lucene例子就完成了。关于lucene的内容非常的多,一一讲解的确需要很多时间,以后会尽快讲解一下更多的内容。不过从上面的基本应用可以看出,lucene API设计的确有些问题,明显违背面向对象的原则,比如通过属性访问字段,而不是通过公共方法来访问。不过由于其应用广泛,性能优越,也顾不了那么多了,先学着吧!