lucene是java语言实现的全文检索工具。使用lucene包括两个步骤,首先建立索引,然后对建立的索引进行检索。本文讲的是建立索引过程。
本文及后面的文章都以磁盘文件为例,进行lucene建立索引、检索的演示。
我们的磁盘上有一堆文件,我们可能有如下的需求:
按文件名搜索文件(使用最多)
按文件路径搜索文件(这个。。。)
按文件类型搜索文件
按文件大小搜索文件
按修改日期搜索文件
按文件内容搜索文件
……
想想我们人是怎么做的?
我们拿一张纸、一支笔,填写下面的表格:
序号 | 文件名 | 文件路径 | 文件类型 | 文件大小 | 修改时间 | 内容 | …… |
填完以后,搜索的时候就可以照着这张纸“按图索骥”了。
在lucene中,这张纸叫做Directory(也就是索引保存的目录),这支笔叫做IndexWriter,表格中一条记录叫做Document,记录中的每项叫做Field。
OK,新建一个Indexer的类,并对外提供index(String indexDir, String... dataDirs)的方法建立索引。
伪代码如下:
public class Indexer { /** * 建立索引 * * @param indexDir * 索引保存路径 * @param dataDirs * 数据文件路径 * @throws Exception */ public void index(String indexDir, String... dataDirs) throws Exception { 实例化IndexerWriter对象:IndexWriter writer = .... for dataDir in dataDirs 建立索引:index(writer, new File(dataDir)) 关闭流 } /** * 对文件(或目录)建立索引 * * @param writer * IndexWriter对象 * @param file * 文件或目录 */ private void index(IndexWriter writer, File file) { 如果file为目录: 对目录下的子文件(夹),调用index(writer, file)方法 如果file为文件: 生成一条Document记录,并将各项填充到该Document中,然后将该Document添加到writer }}
上面的伪代码不难转为Java代码。
package cn.lym.lucene.quickstart.index;import java.io.File;import java.io.FileReader;import java.io.Reader;import org.apache.log4j.LogManager;import org.apache.log4j.Logger;import org.apache.lucene.analysis.standard.StandardAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field.Store;import org.apache.lucene.document.LongField;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;import cn.lym.lucene.quickstart.util.FileUtil;import cn.lym.lucene.quickstart.util.StreamUtil;/** * 提供对磁盘文件建立索引的功能 * * @author liuyimin * */public class Indexer { /** * Logger对象 */ private static final Logger logger = LogManager.getLogger(Indexer.class); /** * 建立索引 * * @param indexDir * 索引保存路径 * @param dataDirs * 数据文件路径 * @throws Exception */ public void index(String indexDir, String... dataDirs) throws Exception { Directory directory = FSDirectory.open(new File(indexDir)); IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new StandardAnalyzer()); IndexWriter writer = new IndexWriter(directory, config); for (String dataDir : dataDirs) { index(writer, new File(dataDir)); writer.commit(); } // 关闭流 StreamUtil.close(writer, directory); } /** * 对文件(或目录)建立索引 * * @param writer * IndexWriter对象 * @param file * 文件或目录 */ private void index(IndexWriter writer, File file) { if (file.isDirectory()) {// 目录,需要递归建立索引 File[] subFiles = file.listFiles(); if (subFiles != null) { for (File subFile : subFiles) { index(writer, subFile); } } } else if (file.isFile()) {// 文件,对文件建立索引 if (logger.isDebugEnabled()) { logger.debug("indexing file: " + file.getAbsolutePath()); } try { Document document = file2Document(file); writer.addDocument(document); } catch (Exception e) { logger.error( "An error occurred while adding a document to indexwriter. File: " + file.getAbsolutePath(), e); } } } /** * 将文件转为lucene的{@link Document}类型 * 其中包括: *
- *
- pathname:路径名 *
- filename:文件名 *
- size:文件大小(字节) *
- type:文件类型 *
- content:文件内容(只有明文文件有,判断是否是明文文件:{@link FileUtil#isPlainTextFile(File)} * ) *
使用了两个工具类。
FileUtil.java
package cn.lym.lucene.quickstart.util;import java.io.File;/** * 文件有关的工具类 * * @author liuyimin * */public class FileUtil { /** * 获得文件类型 * * @param file * @return */ public static String getFileType(File file) { String fileName = file.getName(); int index = fileName.lastIndexOf("."); if (index != -1) { return fileName.substring(index + 1); } return fileName; } /** * 判断文件是否是明文的文件 * * @param file * @return */ public static boolean isPlainTextFile(File file) { // 为了简化,这里只将txt文件作为明文文件 String fileType = getFileType(file); return "txt".equals(fileType); }}
StreamUtil.java
package cn.lym.lucene.quickstart.util;import java.io.Closeable;/** * 流操作有关的工具类 * * @author liuyimin * */public class StreamUtil { /** * 关闭流操作 * * @param closeables */ public static void close(Closeable... closeables) { if (closeables != null) { for (Closeable closeable : closeables) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { } finally { closeable = null; } } } } }}
需要说明的几点:
关于Field。Field可控的参数包括:是否建立索引、是否保存、是否分词以及类型(数值类型或者字符类型)。
常用的Field有下面几种:
StringField:字符类型、建立索引并且不分词。存储与否可控。
LongField:数值类型、建立索引并且不分词。存储与否可控。
TextField:字符类型、建立索引并且分词。存储与否可控。
关于Directory。Directory为索引存放的目录,可以存放在磁盘中(例子中的就是写在磁盘中)也就是FSDirectory;也可以放在内存中,也就是RAMDirectory。
本文的代码可以在 获取。