Lucene是一套用于全文检索和搜寻的开源程式库,一星期的研究实现了简单的基于cms新闻管理系统的全文搜索引擎,自己做笔记记录一下,个人理解可以把lucene当做一个文档型数据库:
首先先了解lucene:
百科是这样说的:Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
lucene常用类
(1)IndexWriter 索引过程的核心组件。这个类负责创建新索引或者打开已有索引,以及向索引中添加、删除或更新索引文档的信息。可以把IndexWriter看做这样一个对象:提供针对索引文件的写入操作,但不能用于读取或搜索索引。IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。
(2)Diretory索引存放的位置,它是一个抽象类,它的子类负责具体制定索引的存储路径。lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。
(3)Analyzer 分析器,主要用于分析搜索引擎遇到的各种文本,Analyzer的工作是一个复杂的过程:把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语(停用词),这里说的无效词语如英文中的“of”、“the”,中文中的“的”、“地”等词语,这些词语在文章中大量出现,但是本身不包含什么关键信息,去掉有利于缩小索引文件、提高效率、提高命中率。分词的规则千变万化,但目的只有一个:按语义划分。这点在英文中比较容易实现,因为英文本身就是以单词为单位的,已经用空格分开;而中文则必须以某种方法将连成一片的句子划分成一个个词语。具体划分方法下面再详细介绍,这里只需了解分析器的概念即可。
// 定义分词器
// Analyzer analyzer = new IKAnalyzer();//分词器
//Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_47);// 简单分词器
// Analyzer analyzer3 = new CJKAnalyzer(VERSION);// 二元切分
Analyzer analyzer = new IKAnalyzer(true);// 语意分词 false关闭智能分词 (对分词的精度影响较大)
//Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);// 创建标准分词器
(4)Document 文档 Document相当于一个要进行索引的单元,可以是文本文件、字符串或者数据库表的一条记录等等,一条记录经过索引之后,就是以一个Document的形式存储在索引文件,索引的文件都必须转化为Document对象才能进行索引。
Document doc = new Document();
Field content = null;
Field title = null;
Field createTime = null;
Field type = null;
doc.add(id);
doc.add(content);
doc.add(title);
doc.add(createTime);
doc.add(type);
(5)Field 一个Document可以包含多个信息域,比如一篇文章可以包含“标题”、“正文”等信息域,这些信息域就是通过Field在Document中存储的。选择field还是比较重要的:
是否分词(Tokenized)
是:对该field存储的内容进行分词,分词的目的,就是为了索引。
比如:商品名称、商品描述、商品价格
否:不需要对field存储的内容进行分词,不分词,不代表不索引,而是将整个内容进行索引。
比如:商品id
是否索引(Indexed)
是:将分好的词进行索引,索引的目的,就是为了搜索。
比如:商品名称、商品描述、商品价格、商品id
否:不索引,也就是不对该field域进行搜索。
是否存储(Stored)
是:将field域中的内容存储到文档域中。存储的目的,就是为了搜索页面显示取值用的。
比如:商品名称、商品价格、商品id、商品图片地址
否:不将field域中的内容存储到文档域中。不存储,则搜索页面中没法获取该field域的值。
比如:商品描述,由于商品描述在搜索页面中不需要显示,再加上商品描述的内容比较多,所以就不需要进行存储。如果需要商品描述,则根据搜索出的商品ID去数据库中查询,然后显示出商品描述信息即可。
根据开发需求选择不同的Filed类型:我找到的常用类型
(6)IndexSearcher 是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具。
(7)IndexReader打开一个Directory读取索引类。
(8)Query 查询,抽象类,必须通过一系列子类来表述检索的具体需求,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。
这个也是比较重要的:查询的条件查询:
BooleanClause用于表示布尔查询子句关系的类,包括:BooleanClause.Occur.MUST,BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.SHOULD。有以下6种组合:
1.MUST和MUST:取得连个查询子句的交集。
2.MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。
3.MUST_NOT和MUST_NOT:无意义,检索无结果。
4.SHOULD与MUST、SHOULD与MUST_NOT:SHOULD与MUST连用时,无意义,结果为MUST子句的检索结果。与MUST_NOT连用时,功能同MUST。
5.SHOULD与SHOULD:意思是 or 表示“或”关系,最终检索结果为所有检索子句的并集。
对于多条件查询还有其他的一些实现类比如 MultiFieldQueryParser.parse()来创建一个Query有多个参数,比较简单查下api就能明白。
(9)QueryParser 解析用户的查询字符串进行搜索,是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。
(10)TopDocs 根据关键字搜索整个索引库,然后对所有结果进行排序,取指定条目的结果。
(11)TokenStream Token分词器Analyzer通过对文本的分析来建立TokenStreams(分词数据流)。TokenStream是由一个个Token(分词组成的数据流)。所以说Analyzer就代表着一个从文本数据中抽取索引词(Term)的一种策略。
(12)AttributeSourceTokenStream即是从Document的域(field)中或者查询条件中抽取一个个分词而组成的一个数据流。TokenSteam中是一个个的分词,而每个分词又是由一个个的属性(Attribute)组成。对于所有的分词来说,每个属性只有一个实例。这些属性都保存在AttributeSource中,而AttributeSource正是TokenStream的父类。
1.依赖注入:
com.janeluo
ikanalyzer
2012_u6
org.apache.lucene
lucene-highlighter
4.7.2
2.实现的增删改查:
package com.yunqi.cms.common;
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.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
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.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;
import com.yunqi.cms.entity.Content;
import com.yunqi.cms.vo.ContentVO;
import com.yunqi.cms.vo.LuceneContentVO;
import com.yunqi.platform.common.page.PageList;
import com.yunqi.platform.utils.LocalDateTimeUtils;
import com.yunqi.platform.utils.SystemConfigure;
/**
*
* @ClassName: LuceneUtils
* @Description: 全文检索
* @author yangqq
* @date 2019年6月15日 上午11:45:00
* @version V1.0
* @Copyright: 2019 www.yunqi.com Inc. All rights reserved.
*/
public class LuceneUtils {
// 定义分词器
// Analyzer analyzer = new IKAnalyzer();//分词器
//Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_47);// 简单分词器
// Analyzer analyzer3 = new CJKAnalyzer(VERSION);// 二元切分
Analyzer analyzer = new IKAnalyzer(true);// 语意分词 false关闭智能分词 (对分词的精度影响较大)
//Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);// 创建一个分词器
public String createIndex(Object t) throws IOException {
if (t == null) {
throw new RuntimeException("所传对象不可为空");
}
LuceneContentVO contentvo = new LuceneContentVO();
if (t instanceof LuceneContentVO) {
contentvo = (LuceneContentVO) t;
// 创建Document对象
Document doc = new Document();
// 获取每列数据
if (contentvo.getId() == null) {
throw new RuntimeException("新闻Id不可为空");
}
if (contentvo.getSiteId() == null) {
throw new RuntimeException("站点信息不存在");
}
if(contentvo.getTitle()==null){
throw new RuntimeException("所存内容名称不可为空");
}
if(contentvo.getType()==null){
throw new RuntimeException("文章所属类型不可为空");
}
Field content = null;
Field title = null;
Field createTime = null;
Field type = null;
Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词
title = new TextField("title", contentvo.getTitle(), Store.YES);
//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词
if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){
content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词
}else{
if (contentvo.getContent() != null) {
content = new TextField("content", contentvo.getContent().toString(),Store.YES);
} else {
content = new TextField("content", "",Store.YES);
}
//title = new StringField("title", contentvo.getTitle(), Store.YES);
}
if (contentvo.getCreateTime() != null) {
createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);
} else {
createTime = new StringField("createTime","", Store.YES);
}
type = new StringField("type", contentvo.getType(), Store.YES);
// 添加到Document中
doc.add(id);
doc.add(content);
doc.add(title);
doc.add(createTime);
doc.add(type);
if (contentvo.getSiteId() != null) {
String path = "";
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + contentvo.getSiteId() + "/lucence";
// 调用,创建索引库
this.write(doc, path);
} else {
throw new RuntimeException("站点信息不存在");
}
System.out.println("id:" + contentvo.getId().toString());
}
return "成功";
}
// 初始化索引
public void initcreate(List
list, Integer siteId) throws IOException { String path = "";
if (siteId != null) {
// 创建储存路径
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + siteId + "/lucence";
delFolder(path);
}
if (list.size() > 0) {
for (LuceneContentVO contentvo : list) {
if (contentvo.getSiteId() == null) {
throw new RuntimeException("站点信息不存在");
}
if (contentvo.getId() == null) {
throw new RuntimeException("新闻Id不可为空");
}
if(contentvo.getTitle()==null){
throw new RuntimeException("所存内容名称不可为空");
}
if(contentvo.getType()==null){
throw new RuntimeException("文章所属类型不可为空");
}
Field content = null;
Field title = null;
Field createTime = null;
Field type = null;
Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词
title = new TextField("title", contentvo.getTitle(), Store.YES);
//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词
if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){
content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词
}else{
if (contentvo.getContent() != null) {
content = new TextField("content", contentvo.getContent().toString(),Store.YES);
} else {
content = new TextField("content", "",Store.YES);
}
}
if (contentvo.getCreateTime() != null) {
createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);
} else {
createTime = new StringField("createTime","", Store.YES);
}
type = new StringField("type", contentvo.getType(), Store.YES);
// 创建Document对象
Document doc = new Document();
// 给title加权
//title.setBoost(4f);
// 添加到Document中
doc.add(id);
doc.add(content);
doc.add(title);
doc.add(createTime);
doc.add(type);
Directory directory = null;
IndexWriterConfig config = null;
IndexWriter iwriter = null;
try {
// 索引库的存储目录()
directory = FSDirectory.open(new File(path));
// 关联当前lucence版本和分值器
config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
// 传入目录和分词器
iwriter = new IndexWriter(directory, config);
iwriter.commit();
// 写入到目录文件中
iwriter.addDocument(doc);
// 提交事务
iwriter.commit();
// 关闭流
iwriter.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
iwriter.close();
}
}
}
}
// 删除指定文件夹下所有文件
// param path 文件夹完整绝对路径
public static boolean delAllFile(String path) {
boolean flag = false;
File file = new File(path);
if (!file.exists()) {
return flag;
}
if (!file.isDirectory()) {
return flag;
}
String[] tempList = file.list();
File temp = null;
for (int i = 0; i < tempList.length; i++) {
if (path.endsWith(File.separator)) {
temp = new File(path + tempList[i]);
} else {
temp = new File(path + File.separator + tempList[i]);
}
if (temp.isFile()) {
temp.delete();
}
if (temp.isDirectory()) {
delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件
delFolder(path + "/" + tempList[i]);// 再删除空文件夹
flag = true;
}
}
return flag;
}
// 删除文件夹
// param folderPath 文件夹完整绝对路径
public static void delFolder(String folderPath) {
try {
delAllFile(folderPath); // 删除完里面所有内容
String filePath = folderPath;
filePath = filePath.toString();
java.io.File myFilePath = new java.io.File(filePath);
myFilePath.delete(); // 删除空文件夹
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 封裝一个方法,将关键字词存储到索引文件中
*
* @param doc
* @throws IOException
*/
public void write(Document doc, String dir) throws IOException {
Directory directory = null;
IndexWriterConfig config = null;
IndexWriter iwriter = null;
try {
// 索引库的存储目录
directory = FSDirectory.open(new File(dir));
// 关联当前lucence版本和分值器
config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
// 传入目录和分词器
iwriter = new IndexWriter(directory, config);
iwriter.commit();
// 写入到目录文件中
iwriter.addDocument(doc);
// 提交事务
iwriter.commit();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
iwriter.close();
}
}
/**
* 修改
*
* @param Object
*/
public void updateDocument(Object t) throws IOException {
if (t == null) {
throw new RuntimeException("所传对象不可为空");
}
try {
LuceneContentVO contentvo = new LuceneContentVO();
if (t instanceof LuceneContentVO) {
contentvo = (LuceneContentVO) t;
if (contentvo.getId() == null) {
throw new RuntimeException("所传对象ID不可为空");
}
String path = "";
if (contentvo.getSiteId() != null) {
// 创建储存路径
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + contentvo.getSiteId() + "/lucence";
}
// 创建Document对象
Document doc = new Document();
if (contentvo.getSiteId() == null) {
throw new RuntimeException("站点信息不存在");
}
if (contentvo.getId() == null) {
throw new RuntimeException("新闻Id不可为空");
}
if(contentvo.getTitle()==null){
throw new RuntimeException("所存内容名称不可为空");
}
if(contentvo.getType()==null){
throw new RuntimeException("文章所属类型不可为空");
}
Field content = null;
Field title = null;
Field createTime = null;
Field type = null;
Field id = new StringField("id", contentvo.getId().toString(),Store.YES);//标题 StringField索引存储不分词
title = new TextField("title", contentvo.getTitle(), Store.YES);
//如果是普通新闻,Content内容不存储,title标题进行分词存储,医生内容的title不分词
if(contentvo.getType().equals(CmsCommon.CMS_NEWS)){
content = new TextField("content", "",Store.YES);//内容 TextField索引存储分词
}else{
if (contentvo.getContent() != null) {
content = new TextField("content", contentvo.getContent().toString(),Store.YES);
} else {
content = new TextField("content", "",Store.YES);
}
//title = new StringField("title", contentvo.getTitle(), Store.YES);
}
if (contentvo.getCreateTime() != null) {
createTime = new StringField("createTime", LocalDateTimeUtils.formatDateTime(contentvo.getCreateTime()), Store.YES);
} else {
createTime = new StringField("createTime","", Store.YES);
}
type = new StringField("type", contentvo.getType(), Store.YES);
// 添加到Document中
doc.add(id);
doc.add(content);
doc.add(title);
doc.add(createTime);
doc.add(type);
this.deleteDocuments(contentvo.getId(), contentvo.getSiteId(),contentvo.getType());
this.write(doc, path);
System.out.println("contentId:" + contentvo.getId().toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 根据名称和类型分页搜索
public PageList
searchPage(Content content) throws IOException { Directory directory = null;
DirectoryReader ireader = null;
PageList
pageList = new PageList<>(); List
cList = new ArrayList<>(); if (content == null) {
throw new RuntimeException("查询新闻信息不可为空");
}
int pageNow = content.getCurrentPage();
int pageSize = content.getPageSize();
try {
// 索引库的存储目录
String path = "";
if (content.getSiteId() != null) {
// 创建储存路径
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + content.getSiteId() + "/lucence";
}
directory = FSDirectory.open(new File(path));
// 读取索引库的存储目录
ireader = DirectoryReader.open(directory);
// 搜索类
IndexSearcher isearcher = new IndexSearcher(ireader);
//开始
BooleanQuery booleanQuery = new BooleanQuery();
// 条件一内容中必须要有title标题
QueryParser parser1 = new QueryParser(Version.LUCENE_47, "title", analyzer);
// 搜索
Query query1 = parser1.parse(content.getTitle());
booleanQuery.add(query1, Occur.SHOULD);
// 条件二内容中包含的词语
QueryParser parser2 = new QueryParser(Version.LUCENE_47, "content", analyzer);
Query query2 = parser2.parse(content.getTitle());
booleanQuery.add(query2, Occur.SHOULD);
TopDocs topDocs = isearcher.search(booleanQuery, pageSize * pageNow);
System.out.println("查询到的条数\t" + topDocs.totalHits);
ScoreDoc[] scores = topDocs.scoreDocs;
int start = (pageNow - 1) * pageSize;
int end = pageSize * pageNow;
if (topDocs.totalHits <= end) {
for (int i = start; i < topDocs.totalHits; i++) {
Document hitDoc = isearcher.doc(scores[i].doc);
ContentVO co = new ContentVO();
co.setId(Integer.parseInt(hitDoc.get("id")));
co.setContent(hitDoc.get("content"));
co.setTitle(hitDoc.get("title"));
co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));
co.setMediaType(hitDoc.get("type"));
cList.add(co);
}
} else {
for (int i = start; i < end; i++) {
Document hitDoc = isearcher.doc(scores[i].doc);
ContentVO co = new ContentVO();
co.setId(Integer.parseInt(hitDoc.get("id")));
co.setContent(hitDoc.get("content"));
co.setTitle(hitDoc.get("title"));
co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));
co.setMediaType(hitDoc.get("type"));
cList.add(co);
}
}
pageList.setList(cList);
pageList.setPageSize(content.getPageSize());
pageList.setCurrentPage(content.getCurrentPage());
pageList.setTotalSize(topDocs.totalHits);
return pageList;
} catch (Exception e) {
e.printStackTrace();
} finally {
ireader.close();
directory.close();
}
return pageList;
}
// 根据id和类型不分页搜索
public List
searchById(LuceneContentVO content) throws IOException { Directory directory = null;
DirectoryReader ireader = null;
List
cList = new ArrayList<>(); if (content == null) {
throw new RuntimeException("查询新闻信息不可为空");
}
try {
// 索引库的存储目录
String path = "";
if (content.getSiteId() != null) {
// 创建储存路径
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + content.getSiteId() + "/lucence";
}
directory = FSDirectory.open(new File(path));
// 读取索引库的存储目录
ireader = DirectoryReader.open(directory);
// 搜索类
IndexSearcher isearcher = new IndexSearcher(ireader);
//开始
BooleanQuery booleanQuery = new BooleanQuery();
// 条件一内容中必须要有id
QueryParser parser1 = new QueryParser(Version.LUCENE_47, "id", analyzer);
// 搜索
Query query1 = parser1.parse(content.getId().toString());
// 条件二内容type属于医生或者普通新闻
QueryParser parser2 = new QueryParser(Version.LUCENE_47, "type", analyzer);
Query query2 = parser2.parse(content.getType());
//Query query2 = parser2.parse("Y");
booleanQuery.add(query1, Occur.MUST);
booleanQuery.add(query2, Occur.MUST);
TopDocs topDocs = isearcher.search(booleanQuery, 1000);
System.out.println("查询到的条数\t" + topDocs.totalHits);
ScoreDoc[] scores = topDocs.scoreDocs;
for (int i = 0; i < topDocs.totalHits; i++) {
Document hitDoc = isearcher.doc(scores[i].doc);
ContentVO co = new ContentVO();
co.setId(Integer.parseInt(hitDoc.get("id")));
co.setContent(hitDoc.get("content"));
co.setTitle(hitDoc.get("title"));
co.setCreateTime(LocalDateTimeUtils.parseDateTime(hitDoc.get("createTime")));
co.setMediaType(hitDoc.get("type"));
cList.add(co);
}
return cList;
} catch (Exception e) {
e.printStackTrace();
} finally {
ireader.close();
directory.close();
}
return cList;
}
/**
* 删除文档
*
* @throws IOException
*/
public void deleteDocuments(Integer id, Integer siteId,String type) throws IOException {
Directory directory = null;
IndexWriterConfig config = null;
IndexWriter iwriter = null;
try {
// 索引库的存储目录
String path = "";
if (siteId != null) {
// 创建储存路径
String resUploadPath = SystemConfigure.getValue("res_upload_path");
path = resUploadPath + siteId + "/lucence";
}
directory = FSDirectory.open(new File(path));
// 关联当前lucence版本和分值器
config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
// 传入目录和分词器
iwriter = new IndexWriter(directory, config);
/*// 删除title中含有关键词“contentId”的文档
iwriter.deleteDocuments(new Term("id", id.toString()));
*/
//开始
BooleanQuery booleanQuery = new BooleanQuery();
// 条件一内容中必须要有id
QueryParser parser1 = new QueryParser(Version.LUCENE_47, "id", analyzer);
// 搜索
Query query1 = parser1.parse(id.toString());
// 条件二内容type属于医生或者普通新闻
QueryParser parser2 = new QueryParser(Version.LUCENE_47, "type", analyzer);
Query query2 = parser2.parse(type);
booleanQuery.add(query1, Occur.MUST);
booleanQuery.add(query2, Occur.MUST);
iwriter.deleteDocuments(booleanQuery);
iwriter.commit();
System.out.println("删除完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
iwriter.close();
}
}
}