最近工作中使用到的全文检索案例,分享下。使用lucene最新版本为3.6,该案例是从磁盘文档建立索引,如下介绍:
lucene简介
Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免
费开放源代码工具;就其本身而论,Lucene是现在并且是这几年,最受欢迎的免费java资讯检索程式库。人们经常提到资讯检索程式库,就像是搜寻引擎,但是不应该将资讯检索程式库与网搜索引擎相混淆
索引和搜索的一些概念的介绍:
假设我们的电脑的目录中含有很多文本文档,我们需要查找哪些文档含有某个关键词。为了实现这种功能,我们首先利用 Lucene 对这个目录中的文档建立索引,然后在建立好的索引中搜索我们所要查找的文档
Document
Document 是用来描述文档的,这里的文档可以指一个 HTML 页面,一封电子邮件,或者是一个文本文件。一个 Document 对象由多个 Field 对象组成的。可以把一个 Document 对象想象成数据库中的一个记录,而每个 Field 对象就是记录的一个字段。
Field
Field 对象是用来描述一个文档的某个属性的,比如一封电子邮件的标题和内容可以用两个 Field 对象分别描述。
Analyzer
在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 Analyzer。Analyzer 把分词后的内容交给 IndexWriter 来建立索引。
IndexWriter
IndexWriter 是 Lucene 用来创建索引的一个核心的类,他的作用是把一个个的 Document 对象加到索引中来。
Directory
这个类代表了 Lucene 的索引的存储的位置,这是一个抽象类,它目前有两个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引的位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引的位置
Query
这是一个抽象类,他有多个实现,比如 TermQuery, BooleanQuery, PrefixQuery. 这个类的目的是把用户输入的查询字符串封装成 Lucene 能够识别的 Query。
Term
Term 是搜索的基本单位,一个 Term 对象有两个 String 类型的域组成。生成一个 Term 对象可以有如下一条语句来完成:Term term = new Term(“fieldName”,”queryWord”); 其中第一个参数代表了要在文档的哪一个 Field 上进行查找,第二个参数代表了要查询的关键词。
IndexSearcher
IndexSearcher 是用来在建立好的索引上进行搜索的。它只能以只读的方式打开一个索引,所以可以有多个 IndexSearcher 的实例在一个索引上进行操作
首先要导入的包有:
lucene-core-3.6.1.jar lucene核心包
IKAnalyzer3.2.8.jar 中文分词器包
lucene-highlighter-3.5.0.jar 高亮包
lucene-memory-3.5.0.jar lucene架包
lucene-analyzers-3.5.0.jar 用于分词 建立索引的jar包
操作类代码(MyLuceneUtil):
package cn.voole.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.queryParser.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.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class MyLuceneUtil {private static File index_file =null;
private static Analyzer analyzer = null;
private static MyLuceneUtil lucence = new MyLuceneUtil();
private static boolean havapath;
private static QueryParser parser ;
private int textmaxlength = 200;
private static String prefixHTML = "<font color='red'>";
private static String suffixHTML = "</font>";
/** 初始化工具 **/
private MyLuceneUtil(){
System.out.println("lucence初始化开始....");
analyzer = new IKAnalyzer();
index_file = new File("d:\\lucene-index");
havapath = (index_file.list().length>=0);
parser = new QueryParser(Version.LUCENE_36, LucenceVo.TITLE,
analyzer);
}/** 返回一个单例 **/
public static MyLuceneUtil instance(){
return lucence;
}
/**
* 索引库中查询
* @param keyword
* @return
* @yijianfeng
*/
public List<LucenceVo> searchIndex(String keyword,int start,int size) {
IndexSearcher searcher = null;
List<LucenceVo> list = new ArrayList<LucenceVo>();
if(!havapath){
return list;
}
try {
IndexReader reader = IndexReader.open(FSDirectory.open(index_file));
searcher = new IndexSearcher(reader);
Query query =parser.parse(keyword);
TopDocs topDocs = searcher.search(query, (size+start));
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
int end = (size+start)<topDocs.totalHits?(size+start):topDocs.totalHits;
for (int i = start; i < end; i++) {
Document doc = searcher.doc(scoreDocs[i].doc);
LucenceVo info = new LucenceVo();
//关键词加亮
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(prefixHTML, suffixHTML);
Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
highlighter.setTextFragmenter(new SimpleFragmenter(textmaxlength));
String DESCRIPTION = highlighter.getBestFragment(analyzer,LucenceVo.DESCRIPTION,doc.get(LucenceVo.DESCRIPTION));
String TITLE = highlighter.getBestFragment(analyzer,LucenceVo.TITLE,doc.get(LucenceVo.TITLE));
if(DESCRIPTION==null)
{
info.setDescription(doc.get(LucenceVo.DESCRIPTION));
}
else
{
info.setDescription(DESCRIPTION);
}
//取消重复加亮节点
TITLE = TITLE.replaceAll(suffixHTML+prefixHTML, "");
DESCRIPTION = info.getDescription().replaceAll(suffixHTML+prefixHTML, "");
//实体赋值
info.setId(doc.get(LucenceVo.ID));
info.setTitle(TITLE);
info.setUrl(doc.get(LucenceVo.URL));
list.add(info);
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
if(searcher!=null)
{
try {
searcher.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return list;
}/**
* 添加列表到索引库中
* @yijianfeng
* @param list
* @throws IOException
*/
public void writeIndex(List<LucenceVo> list) throws IOException {
IndexWriter writer = openIndexWriter();
try {
for (LucenceVo lucenceVo : list) {
Document document = builderDocument(lucenceVo);
writer.addDocument(document);
}
} finally {
writer.close();
}
}/**
* 添加单个到索引库中
* @yijianfeng
* @param LucenceVo
* @throws IOException
*/
public void writeIndex(LucenceVo vo) throws IOException {
IndexWriter writer = openIndexWriter();
try {
Document document = builderDocument(vo);
writer.addDocument(document);
} finally {
writer.close();
}
}
/**
* 删除所有索引库
* @yijianfeng
*/
@SuppressWarnings("deprecation")
public void deleteAllIndex(boolean isdeletefile){
IndexReader reader = null;
if(index_file.exists() && index_file.isDirectory()) {
try{
reader = IndexReader.open(FSDirectory.open(index_file),false);
for(int i = 0; i < reader.maxDoc(); i ++) {
reader.deleteDocument(i);
}
reader.close();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
deleteAllFile();
}
}
/**
* 单条删除索引
*/
@SuppressWarnings("deprecation")
public void deleteIndexByTerm(String id)
{
IndexReader reader = null;
if(index_file.exists() && index_file.isDirectory())
{
try{
reader = IndexReader.open(FSDirectory.open(index_file),false);
Term term = new Term("id",id);
reader.deleteDocuments(term);
}catch(Exception e)
{
e.printStackTrace();
}finally{
if(reader!=null)
{
try{
reader.close();
} catch (IOException e) {
}
}
}
}
}
/**
* 单条更新索引
* @throws IOException
*/
public void updateIndexByTerm(LucenceVo vo) throws IOException
{
IndexWriter writer = openIndexWriter();
try {
Term term = new Term("id",vo.getId());
Document doc = builderDocument(vo);
writer.updateDocument(term, doc);
} finally {
writer.close();
}
}
/**
* 删除所有索引文件
* @yijianfeng
*/
public void deleteAllFile(){
File[] files = index_file.listFiles();
for (int i = 0; i < files.length; i++) {
files[i].delete();
}
}/**
* 类IndexWriterConfig参数说明,第一个参数指定lucene的版本号,第二个参数指定了Analyzer一个实现,也就是指定
* 这个索引是用哪个分词器对文档进行分词
* 类IndexWriter参数说明,第一个参数指定所建索引存放的位置,可以是一个 File 对象,也可以是一个 FSDirectory 对象或者 RAMDirectory 对象
* @return
* @throws IOException
*/private IndexWriter openIndexWriter() throws IOException {
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36,
analyzer);
//索引 设置为追加或者覆盖
indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
return new IndexWriter(FSDirectory.open(index_file), indexWriterConfig);
}@SuppressWarnings("static-access")
private Document builderDocument(LucenceVo lucenceVo) {
Document document = new Document();
Field id = new Field(lucenceVo.ID, String.valueOf(lucenceVo.getId()),
Field.Store.YES, Field.Index.ANALYZED);
Field TITLE = new Field(lucenceVo.TITLE, lucenceVo.getTitle(),
Field.Store.YES, Field.Index.ANALYZED);
Field DESCRIPTION = new Field(lucenceVo.DESCRIPTION, lucenceVo.getDescription(),
Field.Store.YES, Field.Index.ANALYZED);
Field TYPE = new Field(lucenceVo.TYPE, lucenceVo.getType(),
Field.Store.YES, Field.Index.ANALYZED);
Field URL = new Field(lucenceVo.URL, lucenceVo.getUrl(),
Field.Store.YES, Field.Index.ANALYZED);
document.add(id);
document.add(TITLE);
document.add(DESCRIPTION);
document.add(TYPE);
document.add(URL);
return document;
}
}
bean类(LucenceVo)
package cn.voole.util;
public class LucenceVo {
public static final String ID = "id";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String DESCRIPTION = "description";
public static final String URL = "url";
private String id;
private String type;
private String title;
private String description;
private String url;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
测试类(test):
package cn.voole.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class test {/**
* @param args
*/
public static void main(String[] args) {
testwirte();
testsearch();
}
/**
* 向索引库添加数据
*/
public static void testwirte(){
List<LucenceVo> list = new ArrayList<LucenceVo>();
for (int i = 0; i < 10000; i++) {
LucenceVo vo = new LucenceVo();
vo.setId("myid"+i);
vo.setTitle("中国今天新闻"+i);
vo.setDescription("新闻关键字备注"+i);
vo.setType("新闻");
vo.setUrl("http://www.baidu.com/?id="+i);
list.add(vo);
}
try {
MyLuceneUtil.instance().writeIndex(list);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从索引库读取数据
*/
public static void testsearch(){
List<LucenceVo> list = MyLuceneUtil.instance().searchIndex("今天", 0 , 10);
for (int i = 0; i < list.size(); i++) {
LucenceVo vo = list.get(i);
System.out.println(vo.getTitle()+"==="+vo.getDescription());
}
System.out.println(list.size());
}}
lucene性能优化
IndexWriter提供了一些接口可以控制建立索引的操作,另外我们可以先将索引写入RAMDirectory,再批量写入 FSDirectory,不管怎样,目的都是尽量少的文件IO,因为创建索引的最大瓶颈在于磁盘IO。另外选择一个较好的分析器也能提高一些性能。