花了小半天的时间研究了一下Lucene全文检索引擎的使用,看网上的教程动辄十几章着实吓人,想起来N年前学习JDBC的时候买了巨厚的一本专门描写JDBC的书籍,现在想想做数据库编程就那么个套路,其实是很简单的,这个Lucene应该也是一样的,先入了门再关注各个细节的犄角旮旯。
先看项目中要用Lucene的话需要引入哪些jar包,我是用maven自动下载的,依赖包如下:
<dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>4.8.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>4.8.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>4.8.0</version> </dependency> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>4.8.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency>
注意junit不是必须的,只是便于写单元测试我就没有删除。
如果不会使用maven,也可以去Lucene的官方网站下载完整的压缩包后自己复制jar包使用,我的测试示例代码用到的jar包如下图:
复制需要用的jar包就行,不用乱七八糟啥都复制到项目里面的。
下面是测试代码,我是在c:\txts目录里面放了几个txt文本文件去建立全文索引的,找了几个公司的项目投标方案,主要目的是测试在比较多的文字内容的情况下全文索引的速度及检索正确率。
代码如下:
package com.xxx;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class TestLucene {
// 待索引文件存放路径
private static String source_file_path = "c:\\txts";
// 生成的全文索引文件存储路径
private static String indexDir = "c:\\index_dir";
private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 中文分词器IKAnalyzer,从http://code.google.com/p/ik-analyzer/downloads/list 下载最新源码放入项目中使用
private static Analyzer analyzer = new IKAnalyzer();
public void createIndex() {
Directory directory = null;
try {
// 有文件系统或者内存存储方式,这里使用文件系统存储索引数据
directory = new SimpleFSDirectory(new File(indexDir));
// 生成全文索引的配置对象
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_48,analyzer);
// 设置生成全文索引的方式为创建或者追加
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
// 创建真正生成全文索引的writer对象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 读取待索引文件夹下的所有的.txt文件
File sourceFiles = new File(source_file_path);
String txtFiles[] = sourceFiles.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (name.endsWith(".txt")) { // 这里只对.txt文件创建索引
return true;
}
return false;
}
});
// 遍历所有txt文件写入全文索引,如果数据来源是数据库则遍历数据库查询结果集即可
for (String txtFile : txtFiles) {
String file = source_file_path + File.separator + txtFile;
File input_txt_file = new File(file);
System.out.println("开始对" + txtFile + "建立索引");
Document doc = new Document(); // 约等于数据库的一行记录
// 以下生成各个字段的数据值
StringField name = new StringField("filename", input_txt_file.getName(), Store.YES);
TextField content = new TextField("content", readFileContent( file, "gbk"), Store.YES);
StringField path = new StringField("path",input_txt_file.getAbsolutePath(), Store.YES);
StoredField date = new StoredField("date", df.format(new Date()));
LongField size = new LongField("size", input_txt_file.length(),Store.YES);
// 向Document对象中加入各个字段的值
doc.add(name);
doc.add(content);
doc.add(path);
doc.add(date);
doc.add(size);
// 向IndexWriter中增加新的一行记录
indexWriter.addDocument(doc);
// 提交数据内容
indexWriter.commit();
}
indexWriter.close();
directory.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void search() {
String queryString = "test"; //查询条件
String query_fields[]=new String[]{"filename","content"};//对哪几个字段进行查询检索
File file_index_dir = new File(indexDir);
try {
Directory directory = new SimpleFSDirectory(file_index_dir);
IndexReader indexReader = DirectoryReader.open(directory);
// 创建搜索类
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_48, query_fields,analyzer);
queryParser.setDefaultOperator(QueryParser.OR_OPERATOR);//多个关键字时采取 or 操作
Query query = queryParser.parse(queryString);
TopDocs topDocs = indexSearcher.search(query, 10000);//查询前多少条满足条件的数据
System.out.println("一共查到:" + topDocs.totalHits + "记录");
ScoreDoc[] scoreDoc = topDocs.scoreDocs;
for (int i = 0; i < scoreDoc.length; i++) {
// 内部编号
int doc = scoreDoc[i].doc;
System.out.println("内部编号:" + doc);
// 根据文档id找到文档
Document mydoc = indexSearcher.doc(doc);
System.out.println("文档路径:" + mydoc.get("path")); //读取path字段的值
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void searchPage() {
int pageIndex=1;
int pageSize=1;
int start = (pageIndex - 1) * pageSize;
String queryString = "test"; //查询条件
String query_fields[]=new String[]{"filename","content"};//对哪几个字段进行查询检索
File file_index_dir = new File(indexDir);
try {
Directory directory = new SimpleFSDirectory(file_index_dir);
IndexReader indexReader = DirectoryReader.open(directory);
// 创建搜索类
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_48, query_fields,analyzer);
queryParser.setDefaultOperator(QueryParser.OR_OPERATOR);//多个关键字时采取 or 操作
Query query = queryParser.parse(queryString);
int max_result_size = start + pageSize;
TopScoreDocCollector topDocs = TopScoreDocCollector.create(max_result_size, false);
indexSearcher.search(query, topDocs);
int rowCount = topDocs.getTotalHits(); //满足条件的总记录数
int pages = (rowCount - 1) / pageSize + 1; //计算总页数
TopDocs tds = topDocs.topDocs(start, pageSize);
ScoreDoc[] scoreDoc = tds.scoreDocs;
System.out.println("一共查到:" + topDocs.getTotalHits() + "记录,共"+pages+"页");
// 关键字高亮显示的html标签,需要导入lucene-highlighter-x.x.x.jar
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter( "<font color='red'>", "</font>");
Highlighter highlighter = new Highlighter(simpleHTMLFormatter,new QueryScorer(query));
for (int i = 0; i < scoreDoc.length; i++) {
// 内部编号
int doc_id = scoreDoc[i].doc;
System.out.println("内部编号:" + doc_id);
// 根据文档id找到文档
Document mydoc = indexSearcher.doc(doc_id);
// 内容增加高亮显示
TokenStream tokenStream2 = analyzer.tokenStream("content",new StringReader(mydoc.get("content")));
String content = highlighter.getBestFragment(tokenStream2,mydoc.get("content"));
System.out.println("文件名称:"+mydoc.get("filename")+" 高亮内容:" + content);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String readFileContent(String FileName, String charset) {
InputStream fis=null;
BufferedReader reader=null;
StringBuffer result = new StringBuffer();
try {
fis=new FileInputStream(FileName);
reader = new BufferedReader(new InputStreamReader(fis, charset));
String line = new String();
while ((line = reader.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if (null!=reader){
try {
reader.close();
} catch (IOException e) {}
}
if (null!=fis){
try {
fis.close();
} catch (IOException e) {}
}
}
//System.out.println("内容:" + result.toString());
return result.toString();
}
}
里面除了Lucene以外还用到了中文分词器IKAnalyzer,下载网址http://code.google.com/p/ik-analyzer/downloads/list,下载源码包IK Analyzer 2012FF_hf1_source.rar使用,下载后把src里面的文件复制到项目里面就可以了。
记不清是谁说过读书是先读薄再读厚了,使用Lucene的读薄就是先确定索引数据是存到磁盘文件系统还是内存,确定好以后去实例化Directory对象,然后通过配置参数对象去实例化IndexWriter去写待索引文档,对比数据库系统的话,待索引文档对象Document类似于一行记录,上面的示例里面是通过读取硬盘内的文本文件去填充的Document对象,如果要对数据库里面的记录建立全文索引的话,Document的数据来源通过jdbc读取数据库内容即可。
查询更简单了,确定好查询什么内容,确定查哪几个字段就可以了。