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}类型<br/> * 其中包括: * <ul> * <li>pathname:路径名</li> * <li>filename:文件名</li> * <li>size:文件大小(字节)</li> * <li>type:文件类型</li> * <li>content:文件内容(只有明文文件有,判断是否是明文文件:{@link FileUtil#isPlainTextFile(File)} * )</li> * </ul> * * @param file * @return */ private Document file2Document(File file) { Document document = new Document(); document.add(new StringField("pathname", file.getAbsolutePath(), Store.YES)); document.add(new StringField("filename", file.getName(), Store.YES)); document.add(new StringField("type", FileUtil.getFileType(file), Store.YES)); document.add(new LongField("size", file.length(), Store.YES)); document.add(new LongField("lastmodified", file.lastModified(), Store.YES)); if (FileUtil.isPlainTextFile(file)) {// 对明文文件的内容建立索引 try { Reader reader = new FileReader(file); document.add(new TextField("content", reader)); } catch (Exception e) { logger.error("An error occurred while indexing " + file.getAbsolutePath(), e); } } return document; } }
使用了两个工具类。
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。
本文的代码可以在 https://git.oschina.net/coding4j/lucene-quickstart 获取。