一、什么是Lucene
二、Lucece全文检索和数据库检索的区别
三、Lucene的原理
四、Lucene开发原理(索引库与数据库同步)
五、开发步骤
六、Lucene优化
正文
师兄推荐我学习Lucene这门技术,用了两天时间,大概整理了一下相关知识点。
回到顶部
一、什么是Lucene
Lucene即全文检索。全文检索是计算机程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置。当用户查询时根据建立的索引查找,类似于通过字典的检索字表查字的过程。
回到顶部
二、Lucece全文检索和数据库检索的区别
回到顶部
三、Lucene的原理
(1)索引库操作原理
注意:这里面有两个关键的对象:分别是IndexWriter和IndexSearcher。
执行增删改操作用的是IndexWriter对象,执行查询操作用的是IndexSearcher对象。
(2)索引库存放数据原理
注意:Lucece库在4.0之前和4.0之后的API发生了很大变化,这个图中的Index到后面的API已经不建议再用了。后面有相应的替代方法。
比如:原来的写法:
String id = NumericUtils.intToPrefixCoded(1);
new Field(“id”,id,Store.YES,Index.NOT_ANALYZED);
new Field(“title”, “我是陈驰”,Store.YES, Index.NOT_ANALYZED);
new Field("“content”,
“国防科学技术大学(National University of Defense Technology),是中华人民共和国中央军事委员会直属的一所涵盖理学、工学、军事学、管理学、经济学、哲学、文学、教育学、法学、历史学等十大学科门类的综合性全国重点大学”,
Store.YES,
Index.ANALYZED);
后来的写法:
new IntField(“id”, 1, Store.YES);
new StringField(“title”, “我是陈驰”, Store.YES);
new TextField(
“content”,
“国防科学技术大学(National University of Defense Technology),是中华人民共和国中央军事委员会直属的一所涵盖理学、工学、军事学、管理学、经济学、哲学、文学、教育学、法学、历史学等十大学科门类的综合性全国重点大学”,
Store.YES);
回到顶部
四、Lucene开发原理(索引库与数据库同步)
数据库与索引库中存放相同的数据,可以使用数据库中存放的ID用来表示和区分同一条数据。
–数据库中用来存放数据
–索引库中用来查询、检索
检索库支持查询检索多种方式,
特点:
1:由于是索引查询(通过索引查询数据),检索速度快,搜索的结果更加准确
2:生成文本摘要,摘要截取搜索的文字出现最多的地方
3:显示查询的文字高亮
4:分词查询等
注意:添加了索引库,并不意味着不往数据库中存放数据,数据库的所有操作仍和以前一样。只不过现在多维护一个索引库,在查询的时候可以提高效率。
所有的数据(对象),我们都要存到数据库中。对于要进行搜索的数据,还要存到索引库中,以供搜索。一份数据同时存到数据库与索引库中(格式不同),就要想办法保证他们的状态一致。否则,就会影响搜索结果。
对于上一段提出的问题:保证索引库中与数据库中的数据一致(只要针对要进行搜索的数据)。我们采用的方法是,在数据库中做了相应的操作后,在索引库中也做相应的操作。具体的索引库操作,是通过调用相应的IndexDao方法完成的。IndexDao类似于数据库层的Dao。
索引库中存放的数据要转换成Document对象(每条数据就是一个Document对象),并向Document对象中存放Field对象(每条数据对应的字段,例如主键ID、所属单位、图纸类别、文件名称、备注等),将每个字段中的值都存放到Field对象中。
回到顶部
五、开发步骤
(1)需要的jar包
(2)需要的配置文件(注:这里我用的是IKAnalyzer,是第三方的中文分词器,庖丁分词,中文分词,特点:扩展新的词,自定义停用词。只有用该分词器才用到以下配置文件。)
l IKAnalyzer.cfg.xml
复制代码
IK Analyzer 扩展配置 mydict.dic;
ext_stopword.dic
复制代码 l ext.dic(扩展词库)
l stopword.dic(停用词库)
(3)一个简单的例子(用的标准分词器StandardAnalyzer,所以暂时没有用到上面的配置文件,标准分词器是汉字一个一个分的,英语还是按单词)
复制代码
import java.io.File;
import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
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.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
/**
*/
public class TestLucene {
/**
* 使用IndexWriter对数据创建索引
* @throws IOException
*/
@Test
public void testCreateIndex() throws IOException {
// 索引存放的位置...
Directory d = FSDirectory.open(new File("indexDir/"));
// 索引写入的配置
Version matchVersion = Version.LUCENE_CURRENT;// lucene当前匹配的版本
Analyzer analyzer = new StandardAnalyzer(matchVersion);// 分词器
IndexWriterConfig conf = new IndexWriterConfig(matchVersion, analyzer);
// 构建用于操作索引的类
IndexWriter indexWriter = new IndexWriter(d, conf);
// 通过IndexWriter来创建索引
// 索引库里面的数据 要遵守一定的结构(索引结构,document)
Document doc = new Document();
/**
* 1.字段的名称 2.该字段的值 3.字段在数据库中是否存储
* StringField是一体的
* TextField是可分的
*/
IndexableField field = new IntField("id", 1, Store.YES);
IndexableField title = new StringField("title", "java培训零基础开始 从入门到精通",
Store.YES);
IndexableField content = new TextField(
"content",
"java培训,中软国际独创实训模式,三免一终身,学java多项保障让您无后顾之忧。中软国际java培训,全日制教学,真实项目实战,名企定制培训,四个月速成java工程师!",
Store.YES);
doc.add(field);
doc.add(title);
doc.add(content);
// document里面也有很多字段
indexWriter.addDocument(doc);
indexWriter.close();
}
/**
* 使用IndexSearcher对数据创建索引
*
* @throws IOException
*/
@Test
public void testSearcher() throws IOException {
// 索引存放的位置...
Directory d = FSDirectory.open(new File("indexDir/"));
// 通过indexSearcher去检索索引目录
IndexReader indexReader = DirectoryReader.open(d);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 这是一个搜索条件,根据这个搜索条件我们来进行查找
// term是根据哪个字段进行检索,以及字段对应值
//================================================
//注意:这样是查询不出,只有单字才能查询出来
Query query = new TermQuery(new Term("content", "培训"));
// 搜索先搜索索引目录
// 找到符合条件的前100条数据
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println("总记录数:" + topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//得分采用的是VSM算法
System.out.println("相关度得分:" + scoreDoc.score);
//获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
int doc = scoreDoc.doc;
//使用编号,获取真正的数据
Document document = indexSearcher.doc(doc);
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
}
}
复制代码
(4)实现Lucene的CURD操作
先写一个LuceneUtils类:
复制代码
import java.io.File;
import java.io.IOException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
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.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
public class LuceneUtils {
public static Directory d = null;
public static IndexWriterConfig conf = null;
public static Version matchVersion = null;
public static Analyzer analyzer = null;
static{
try {
d = FSDirectory.open(new File(Constant.FILEURL));
matchVersion = Version.LUCENE_44;
//注意:该分词器是单字分词
analyzer = new StandardAnalyzer(matchVersion);
conf = new IndexWriterConfig(matchVersion, analyzer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* @return 返回版本信息
*/
public static Version getMatchVersion() {
return matchVersion;
}
/**
*
* @return 返回分词器
*/
public static Analyzer getAnalyzer() {
return analyzer;
}
/**
*
* @return 返回用于操作索引的对象
* @throws IOException
*/
public static IndexWriter getIndexWriter() throws IOException{
IndexWriter indexWriter = new IndexWriter(d, conf);
return indexWriter;
}
/**
*
* @return 返回用于读取索引的对象
* @throws IOException
*/
public static IndexSearcher getIndexSearcher() throws IOException{
IndexReader r = DirectoryReader.open(d);
IndexSearcher indexSearcher = new IndexSearcher(r);
return indexSearcher;
}
}
复制代码
再写一个Constant常量类:
public class Constant {
public static String FILEURL = “E://indexDir/news”;
}
封装一个实体类Article:
复制代码
public class Article {
private int id;
private String title;
private String author;
private String content;
private String link;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
复制代码
由于需要实体类和document对象之间彼此转换,所以再写一个转换的工具类ArticleUtils:
复制代码
import org.apache.lucene.document.Document;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import com.tarena.bean.Article;
public class ArticleUtils {
/**
* 将artitle转换成document
* @param article
* @return
*/
public static Document articleToDocument(Article article) {
Document document = new Document();
IntField idField = new IntField(“id”, article.getId(), Store.YES);
//StringField不进行分词(整体算一个)
StringField authoField = new StringField(“author”, article.getAuthor(), Store.YES);
StringField linkField = new StringField(“link”, article.getLink(), Store.YES);
//TextField进行分词
TextField titleField = new TextField(“title”, article.getTitle(), Store.YES);
//==============================================
//注意:这里可以添加权重值,默认是1f,添加之后检索的时候排名就会靠前
titleField.setBoost(4f);
TextField contentField = new TextField(“content”, article.getContent(), Store.YES);
document.add(idField);
document.add(authoField);
document.add(linkField);
document.add(titleField);
document.add(contentField);
return document;
}
/**
* 将document转换成article
* @param document
* @return
*/
public static Article documentToArticle(Document document){
Article article = new Article();
article.setId(Integer.parseInt(document.get("id")));
article.setAuthor(document.get("author"));
article.setLink(document.get("link"));
article.setTitle(document.get("title"));
article.setContent(document.get("content"));
return article;
}
}
复制代码
以上准备工作都做完,接下来我们就可以写一个LuceneDao,进行增删改查(分页)操作。
复制代码
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.login.Configuration;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
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 com.tarena.bean.Article;
import com.tarena.utils.ArticleUtils;
import com.tarena.utils.LuceneUtils;
/**
/
public class LuceneDao {
/*
* 增删改索引都是通过IndexWriter对象来完成
*/
public void addIndex(Article article) {
try {
IndexWriter indexWriter = LuceneUtils.getIndexWriter();
indexWriter.addDocument(ArticleUtils.articleToDocument(article));
indexWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除索引,根据字段对应的值删除
* @param fieldName
* @param fieldValue
* @throws IOException
*/
public void deleteIndex(String fieldName, String fieldValue) throws IOException {
IndexWriter indexWriter = LuceneUtils.getIndexWriter();
//使用词条删除
Term term = new Term(fieldName, fieldValue);
indexWriter.deleteDocuments(term);
indexWriter.close();
}
/**
* 先删除符合条件的记录,再创建一个新的纪录
* @param fieldName
* @param fieldValue
* @param article
* @throws IOException
*/
public void updateIndex(String fieldName, String fieldValue, Article article) throws IOException {
IndexWriter indexWriter = LuceneUtils.getIndexWriter();
Term term = new Term(fieldName, fieldValue);
Document doc = ArticleUtils.articleToDocument(article);
/**
* 1.设置更新的条件
* 2.设置更新的内容和对象
*/
indexWriter.updateDocument(term, doc);
indexWriter.close();
}
/**
* 查询是通过IndexSearch提供的(分页)
*/
public List findIndex(String keywords, int start, int count) {
try {
IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
//===========================================================
//这里是第二种query方式,不是termQuery
QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), new String[] { "title",
"content" }, LuceneUtils.getAnalyzer());
Query query = queryParser.parse(keywords);
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println("总记录数:" + topDocs.totalHits);
//表示返回的结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List list = new ArrayList();
int min = Math.min(scoreDocs.length, start + count);
for (int i = start; i < min; i++) {
System.out.println("相关度得分:"+scoreDocs[i].score);
//获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
int doc = scoreDocs[i].doc;
//使用编号,获取真正的数据
Document document = indexSearcher.doc(doc);
Article article = ArticleUtils.documentToArticle(document);
list.add(article);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
复制代码
对LuceneDao进行测试:
复制代码
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import com.tarena.bean.Article;
import com.tarena.dao.LuceneDao;
public class TestLuceneDao {
private LuceneDao dao = new LuceneDao();
@Test
public void addIndex() {
for (int i = 0; i <= 25; i++) {
Article article = new Article();
article.setId(i);
article.setTitle("腾讯qq");
article.setAuthor("马化腾");
article.setContent("腾讯网(www.QQ.com)是中国浏览量最大的中文门户网站,是腾讯公司推出的集新闻信息、互动社区、娱乐产品和基础服务为一体的大型综合门户网站。腾讯网服务于全球华人...");
article.setLink("http://www.qq.com/");
dao.addIndex(article);
}
}
@Test
public void findIndex() {
String keywords = "第一";
List list = dao.findIndex(keywords, 0, 30);
for (Article article : list) {
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
System.out.println(article.getAuthor());
System.out.println(article.getLink());
}
}
@Test
public void deleteIndex(){
try {
dao.deleteIndex("author", "陈驰");
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void updateIndex(){
String fieldName = "title";
String fieldValue = "qq";
Article article = new Article();
article.setId(1);
article.setAuthor("陈驰");
article.setLink("http://www.baidu.com");
article.setTitle("天下第一");
article.setContent("天下第一一一一一一");
try {
dao.updateIndex(fieldName, fieldValue, article);
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
由此,简单的CURD操作就完成了。
(5)关于分词器
相同一段文本,在不同的分词器下面分成的词是不尽相同的,前面我们的程序中分别用了StandardAnalyzer,以及IKAnalyzer。那么分词器究竟有什么作用呢?
在创建索引时会用到分词器,在使用字符串搜索时也会用到分词器,这两个地方要使用同一个分词器,否则可能会搜索不出结果。
Analyzer(分词器)的作用是把一段文本中的词按规则取出所包含的所有词。对应的是Analyzer类,这是一个抽象类,切分词的具体规则是由子类实现的,所以对于不同的语言(规则),要用不同的分词器。如下图:
下面分别用过三种分词器,演示分词结果:
复制代码
import java.io.IOException;
import java.io.StringReader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class TestAnalyzer {
public static void main(String[] args) {
//单字分词器
//Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_44);
//二分法分词器
//Analyzer analyzer = new CJKAnalyzer(Version.LUCENE_44);
//第三方的中文分词器,庖丁分词,中文分词,特点:扩展新的词,自定义停用词
Analyzer analyzer = new IKAnalyzer();
String text = "腾讯网(www.QQ.com)是中国浏览量最大的中文门户网站,是腾讯公司推出的集新闻信息、互动社区、娱乐产品和基础服务为一体的大型综合门户网站。腾讯网服务于全球华人...";
try {
testAnalyzer(analyzer, text);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 分词器的作用
*
* @throws IOException
*/
public static void testAnalyzer(Analyzer analyzer, String text)
throws IOException {
System.out.println("当前使用的分词器:" + analyzer.getClass().getSimpleName());
TokenStream tokenStream = analyzer.tokenStream("content",
new StringReader(text));
tokenStream.addAttribute(CharTermAttribute.class);
//这里不写这一句,会报空指针异常
tokenStream.reset();
while (tokenStream.incrementToken()) {
CharTermAttribute charTermAttribute = tokenStream
.getAttribute(CharTermAttribute.class);
System.out.println(new String(charTermAttribute.toString()));
}
}
}
前使可以看到程序中分贝用了三种分词器(单字分词器,即标准分词器,二分法分词器,庖丁分词器),处理相同一段文本——腾讯网
显然最后一种分词器,对中文来说分词效果最好。
(6)关于相关度排序
1,相关度得分是在查询时根据查询条件实进计算出来的
2,如果索引库数据不变,查询条件不变,查出的文档得分也不变
在这里,有两种方式改变相关度排序:
1)通过改变文档Boost值来改变排序结果。Boost是指索引建立过程中,给整篇文档或者文档的某一特定属性设定的权值因子,在检索时,优先返回分数高的。通过Document对象的setBoost()方法和Field对象的setBoost()方法,可以分别为Document和Field指定Boost参数。不同在于前者对文档中每一个域都修改了参数,而后者只针对指定域进行修改。默认值为1F,一般不做修改。前面的ArticleUtils类中已经举例。
复制代码
//在添加的时候改变权重值,可以对每个document 的属性进行添加,
//注:如果添加的索引值没有进行分词,则不能改变权限值.
Document document=new Document();
document.add(new StringField(“id”,article.getId(),Store.YES));
StringField field=new StringField(“title”,article.getTitle(),Store.YES);
TextField textField=new TextField(“content”,article.getContent(),Store.YES);
textField.setBoost(5F);
document.add(field);
document.add(textField);
return document;
复制代码
2)使用Sort对象定制排序。Sort支持的排序功能以文档当中的域为单位,通过这种方法,可以实现一个或者多个不同域的多形式的值排序。
复制代码
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
String fields[] = { “title” };
QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), fields,
LuceneUtils.getAnalyzer());
// 不同的规则构造不同的子类…
// title:keywords ,content:keywords
Query query = queryParser.parse(“抑郁症”);
Sort sort = new Sort();
// 升序
// SortField sortField=new SortField(“id”, Type.INT);
// 降序
SortField sortField = new SortField(“id”, Type.INT, true);
// 设置排序的字段…
sort.setSort(sortField);
TopDocs topDocs = indexSearcher.search(query, 100, sort);
//注意:String类型和Int类型在比较排序的时候不同
//例如:Int:123>23
// String:”123”<“23”
复制代码
(7)关于过滤
使用Filter可以对搜索结果进行过滤以获得更小范围的结果。使用Filter对性能的影响很大(有可能会使查询慢上百倍)。但也可使用相应的查询实现一样的效果。
复制代码
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
String fields[] = { “title” };
QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), fields,
LuceneUtils.getAnalyzer());
// 不同的规则构造不同的子类…
// title:keywords ,content:keywords
Query query = queryParser.parse(“抑郁症”);
/**
*/
// filter 是一个抽象类,定义不同的filter 相当于是不同的过滤规则…
Filter filter = NumericRangeFilter
.newIntRange(“id”, 1, 10, false, true);
TopDocs topDocs = indexSearcher.search(query, filter, 100);
复制代码
(8)关于查询(这个非常非常重要,这是结合前面的分词器分出的词进行不同的查询的)
复制代码
import org.apache.lucene.document.Document;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import com.tarena.utils.LuceneUtils;
/**
*
*/
public class TestQuery {
public static void main(String[] args) throws Exception {
// testQuery();
/**
* 第一种查询,TermQuery
* 这是关键字查询
* 如果按照author查,因为author没有分词,所以查"马化腾"可以查询出来
* 如果按照content查,因为content分词了,如果是单字分词器,只能通过某一个字查出来,比如"中"
*/
//Query query=new TermQuery(new Term("content","中"));
/**
* 第二种查询:字符串搜索..
* 使用查询字符串:QueryParser+ MultiFieldQueryParser的查询方式
* 1、QueryParser:只在一个字段中查询
* 2、MultiFieldQueryParser:可以在多个字段查询
* 用来查询可以分词的字段,只要你输入的一段文本中包含分词,就会检索出来
*/
// String[] fields={“content”};
//
// QueryParser queryParser=new
// MultiFieldQueryParser(LuceneUtils.getMatchVersion(),fields,LuceneUtils.getAnalyzer());
// Query query=queryParser.parse(“中文”);
/**
* 第三种查询:查询所有..
*/
// Query query=new MatchAllDocsQuery();
/**
* 第四种查询:范围查询,可以使用此查询来替代过滤器...
*/
// 我们完成一种需求有两种方式,我们推荐用这种...性能比filter要高
// Query query=NumericRangeQuery.newIntRange("id", 1, 10, true, true);
/**
* 第五种查询:通配符。。。
*/
// ?代表单个任意字符,* 代表多个任意字符...
// Query query=new WildcardQuery(new Term("title", "luce*"));
/**
* 第六种查询:模糊查询..。。。
*/
// author String
/*
* 1:需要根据查询的条件
*
* 2:最大可编辑数 取值范围0,1,2 允许我的查询条件的值,可以错误(或缺少)几个字符...
*
*/
// Query query = new FuzzyQuery(new Term("author", "爱新觉罗杜小"), 1);
/**
*
* 第七种查询:短语查询
*
*/
// PhraseQuery query=new PhraseQuery();
// //(1)直接指定角标...
// // query.add(new Term("title","solr"),0);
// // query.add(new Term("title","全"),8);
// (2)设置两个短语之间的最大间隔数...
// //设置间隔数范围越大,它被匹配的结果就越多,性能也就越慢..
// query.add(new Term("title","solr"));
// query.add(new Term("title","全"));
// query.setSlop(18);
// 第八种查询:布尔查询
BooleanQuery query = new BooleanQuery();
// id 1~10
Query query1 = NumericRangeQuery.newIntRange("id", 1, 10, true, true);
Query query2 = NumericRangeQuery.newIntRange("id", 5, 15, true, true);
// select * from table where title=? or content=?
// 必须满足第一个条件...
query.add(query1, Occur.MUST);
// 可以满足第二个条件
query.add(query2, Occur.SHOULD);
testQuery(query);
}
public static void testQuery(Query query) throws Exception {
IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println("总记录数:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
System.out.println(document.get("author"));
System.out.println(document.get("link"));
}
}
}
复制代码
其中, 布尔查询很重要,可以实现像京东、淘宝上面的多级检索的效果,示例代码如下:
复制代码
//封装查询条件(使用BooleanQuery对象,连接多个查询条件)
BooleanQuery query = new BooleanQuery();
//条件一(所属单位)
if(StringUtils.isNotBlank(projId)){
//词条查询
TermQuery query1 = new TermQuery(new Term(“projId”, projId));
//Occur.SHOULD相当于or
query.add(query1, Occur.MUST);//Occur.MUST相当与sql语句的and
}
//条件二(图纸类别)
if(StringUtils.isNotBlank(belongTo)){
//词条查询
TermQuery query2 = new TermQuery(new Term(“belongTo”, belongTo));
query.add(query2, Occur.MUST);//相当与sql语句的and
}
//条件三(文件名称和文件描述)
if(StringUtils.isNotBlank(queryString)){
//多个字段进行检索的时候,查询使用QueryPaser
//要是直接new QueryParser(),也可以,但是只能查询一个字段
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_CURRENT,new String[]{"fileName","comment"},Configuration.getAnalyzer());
Query query3 = queryParser.parse(queryString);
query.add(query3, Occur.MUST);//相当与sql语句的and
}
复制代码
(9)设置文字高亮
效果图:
1)高亮需要的jar包
highlighter\lucene-highlighter-4.4.0.jar
memory\lucene-memory-4.4.0.jar
2)高亮的特点
1、高亮将文本生成一段摘要,用于搜索,并把摘要中的关键词高亮显示
2、摘要的大小可以配置,默认100个字符。
3、文本实现高亮的效果,就是在需要高亮的文字前后加前缀与后缀(类似Html)
示例如下:
复制代码
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
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.highlight.Formatter;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.wltea.analyzer.lucene.IKAnalyzer;
import com.tarena.bean.Article;
import com.tarena.dao.LuceneDao;
import com.tarena.utils.ArticleUtils;
import com.tarena.utils.LuceneUtils;
public class TestHighLight {
public static void main(String[] args) {
String keywords = "中华人民共和国";
List list = findIndex(keywords, 0, 10);
for (Article article : list) {
System.out.println(article.getId());
System.out.println(article.getTitle());
System.out.println(article.getContent());
System.out.println(article.getAuthor());
System.out.println(article.getLink());
}
}
/**
* 查询是通过IndexSearch提供的(分页)
*/
public static List findIndex(String keywords, int start, int count) {
Analyzer analyzer = new IKAnalyzer();
try {
IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
// ===========================================================
// 这里是第二种query方式,不是termQuery
QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), new String[] { "title",
"content" }, analyzer);
Query query = queryParser.parse(keywords);
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println("总记录数:" + topDocs.totalHits);
/**
* 添加设置文字高亮begin 使用lucene自带的高亮器进行高亮显示
*/
// html页面高亮显示的格式化,默认是
Formatter formatter = new SimpleHTMLFormatter(
"", "");
// 执行查询条件,因为高亮的值就是查询条件
Scorer scorer = new QueryScorer(query);
Highlighter highlighter = new Highlighter(formatter, scorer);
// 设置文字摘要,此时摘要大小
int fragmentSize = 100;
Fragmenter fragmenter = new SimpleFragmenter(fragmentSize);
highlighter.setTextFragmenter(fragmenter);
/** 添加设置文字高亮end */
// 表示返回的结果集
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
List list = new ArrayList();
int min = Math.min(scoreDocs.length, start + count);
for (int i = start; i < min; i++) {
//System.out.println("相关度得分:" + scoreDocs[i].score);
// 获取查询结果的文档的惟一编号,只有获取惟一编号,才能获取该编号对应的数据
int doc = scoreDocs[i].doc;
// 使用编号,获取真正的数据
Document document = indexSearcher.doc(doc);
/** 获取文字高亮的信息 begin */
// 获取文字的高亮,一次只能获取一个字段高亮的结果,如果获取不到,返回null值
// 高亮之后的title
// 注意:如果这个字段当中没有包含搜索关键字,你对这个字段的值进行高亮,返回的是null
String title = highlighter.getBestFragment(
analyzer, "title",
document.get("title"));
// 如果null表示没有高亮的结果,如果高亮的结果,应该将原值返回
if (title == null) {
title = document.get("title");
if (title != null && title.length() > fragmentSize) {
// 截串,从0开始
title = title.substring(0, fragmentSize);
}
}
System.out.println("-------title:" + title);
// 高亮之后的content
// 注意:如果这个字段当中没有包含搜索关键字,你对这个字段的值进行高亮,返回的是null
String content = highlighter.getBestFragment(
analyzer, "content",
document.get("content"));
// 如果null表示没有高亮的结果,如果高亮的结果,应该将原值返回
if (content == null) {
content = document.get("content");
if (content != null && content.length() > fragmentSize) {
// 截串,从0开始
content = content.substring(0, fragmentSize);
}
}
System.out.println("--------content:" + content);
/** 获取文字高亮的信息 end */
Article article = new Article();
article.setId(Integer.parseInt(document.get("id")));
article.setAuthor(document.get("author"));
article.setLink(document.get("link"));
article.setTitle(title);//高亮之后的
article.setContent(content);//高亮之后的
list.add(article);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
复制代码
回到顶部
六、Lucene优化
(1)MergePolicy 设置合并规则
复制代码
public void testOptimise1() throws IOException {
// 可以通过indexWriterConfig这个对象进行优化
// 在lucene4.0之后的版本会对索引进行自动的优化
// 通过改几个配置
Directory d = FSDirectory.open(new File(Constant.FILEURL));
IndexWriterConfig conf = new IndexWriterConfig(
LuceneUtils.getMatchVersion(), LuceneUtils.getAnalyzer());
// 在lucene里面是0配置的
// 通过设置对象的参数来进行配置
LogDocMergePolicy mergePolicy = new LogDocMergePolicy();
/**
*
* 1:mergeFactor
*
* 当这个值越小,更少的内存被运用在创建索引的时候,搜索的时候越快,创建索引的时候越慢..
*
* 当这个值越大,更多的内存被运用在创建索引的时候,搜索的时候越慢,创建的时候越快...
*
*
* smaller value 2 < smaller value <10
*
*/
// 设置索引的合并因子...
mergePolicy.setMergeFactor(6);
conf.setMergePolicy(mergePolicy);
IndexWriter indexWriter = new IndexWriter(d, conf);
}
复制代码
(2)排除停用词,排除停用,被分词器过滤掉,词就不会建立索引,索引文件就会变小,这样搜索的时候就会变快。
(3)将索引数据分区存放
(4)将索引放在内存当中,而不是硬盘当中
Lucene的API接口设计的比较通用,输入输出结构都很像:
数据库的表==>记录==>字段。
所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。
Lucene的索引存储位置使用的是一个接口(抽象类),也就可以实现各种各样的实际存储方式(实现类、子类),比如存到文件系统中,存在内存中、存在数据库中等等。
• Lucene提供了两个子类:FSDirectory与RAMDirectory。
FSDirectory:在文件系统中,是真实的文件夹与文件。
RAMDirectory:在内存中,是模拟的文件夹与文件。
• RAMDirectory与FSDirectory相比:
1、因为没有IO操作,所以速度快(优点)。
2,因为在内存中,所以在程序退出后索引库数据就不存在了(缺点)。
复制代码
public void testOptimise4() throws IOException, ParseException {
// 索引在硬盘里面...
Directory directory1 = FSDirectory.open(new File(Constant.FILEURL));
IOContext ioContext = new IOContext();
// 索引放在内存当中...
Directory directory = new RAMDirectory(directory1, ioContext);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
String fields[] = { "title" };
QueryParser queryParser = new MultiFieldQueryParser(
LuceneUtils.getMatchVersion(), fields,
LuceneUtils.getAnalyzer());
// 不同的规则构造不同的子类..
// title:keywords ,content:keywords
Query query = queryParser.parse("抑郁症");
TopDocs topDocs = indexSearcher.search(query, 100);
System.out.println(topDocs.totalHits);
}