个人学习SpringBoot系列 Lucene篇
Github Link: https://github.com/panjianlong13/SpringBoot-SpringCloud/tree/master/spring-boot-lucene-demo
Lucene介绍
Lucene是什么
Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包,提供了完整的查询引擎和索引引擎,部分文本分析引擎
倒排索引
也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
Lucene 实现全文检索的流程
index 一个索引存放在一个目录中
segment 一个索引中可以有多个段,段与段之间是独立的,添加新的文档可能产生新段,不同的段可以合并成一个新段
document 文档是创建索引的基本单位,不同的文档保存在不同的段中,一个段可以包含多个文档
field 域,一个文档包含不同类型的信息,可以拆分开索引
term 词,索引的最小单位,是经过词法分析和语言处理后的数据。
创建文档对象
1.获取原始内容的目的是为了索引,在索引前,需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),
域中存储内容;
2.我们可以将磁盘上的一个文件当成一个 document, Document 中包括一些Field(file_name 文件名称, file_path
文件路径, file_size 文件大小, file_content 文件内容);
3.每一个 Document 可以有多个 Field,同一个Document,可以有相同的 Field(域名和域值都相同);
4.每一个 Document 都有一个唯一的编号,就是文档 id;
分析文档
1.将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词,
将字母转为小写,去除标点符号,去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词;
2.每一个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term; term中包含两部分,一部分是文档的域名, 另一部分是单词的内容;
3.Field 域的属性是否分析: 是否对域的内容进行分词处理;
是否分析: 是否对域的内容进行分词处理;
是否索引: 将 Field 分析后的词或整个 Field 值进行索引,只有建立索引,才能搜索到;
是否存储: 存储在文档中的 Field 才可以从 Document 中获取;
创建索引
对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document;
这种索引的结构叫倒排索引结构;
传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大,搜索慢;
倒排索引结构是根据内容(词语)找文档; 顺序扫描方法是根据文档查找里面的内容;
查询索引
创建查询
用户输入查询关键字执行搜索前,需要先创建一个查询对象,查询对象中可以指定查询要搜索的 Field 文档域,查询关键字等,
查询对象会生成具体的查询语法。
例如: fileName:lucene
: 表示要搜索Field域的内容为"lucene"的文档;
执行查询
根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表;
比如: fileName:lucene
的搜索过程: 在索引上查找域为 fileName, 并且关键字为Lucene的term, 并根据 term 找到
文档 id 列表;
支持中文分词器(IKAnalyzer)
从一个 Reader 字符流开始,创建一个基于 Reader 的 Tokenizer分词器,经过三个 TokenFilter,生成语汇单元 Tokens;
如果要查看分词器的分词效果,只需要看Tokenstream
中的内容就可以了,每个分词器都有一个方法tokenStream
,返回一个tokenStream
对象;
Lucene实战
新建SpringBoot项目
在pom.xml中添加依赖
在测试类中配置Lucene
public class BaseTest {
private Directory directory;
private IndexReader indexReader;
private IndexSearcher indexSearcher;
@Before
public void setUp() throws IOException {
System.out.println("setUp");
// 索引存放的位置,设置在当前目录中
directory = FSDirectory.open(Paths.get("indexDir/"));
// 创建索引的读取器
indexReader = DirectoryReader.open(directory);
// 创建一个索引的查找器,来检索索引库
indexSearcher = new IndexSearcher(indexReader);
}
@After
public void tearDown() throws Exception {
System.out.println("tearDown");
if (indexReader != null) {
indexReader.close();
}
}
/**
* 执行查询,并打印查询到的记录数
*
* @param query
* @throws IOException
*/
public void executeQuery(Query query) throws IOException {
System.out.println("executeQuery");
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("id:" + document.get("id"));
System.out.println("title:" + document.get("title"));
System.out.println("content:" + document.get("content"));
}
}
/**
* 分词打印
*
* @param analyzer
* @param text
* @throws IOException
*/
public void printAnalyzerDoc(Analyzer analyzer, String text) throws IOException {
System.out.println("printAnalyzerDoc");
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text));
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
try {
tokenStream.reset();
while (tokenStream.incrementToken()) {
System.out.println(charTermAttribute.toString());
}
tokenStream.end();
} finally {
tokenStream.close();
analyzer.close();
}
}
创建索引
/**
* 创建索引
*
* @throws IOException
*/
@Test
public void indexWriterTest() throws IOException {
System.out.println("indexWriterTest");
long start = System.currentTimeMillis();
// 索引存放的位置,设置在当前目录中
Directory directory = FSDirectory.open(Paths.get("indexDir/"));
// 在 6.6 以上版本中 version 不再是必要的,并且,存在无参构造方法,可以直接使用默认的 StandardAnalyzer 分词器。
Version version = Version.LUCENE_7_1_0;
// Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
// Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
// Analyzer analyzer = new ComplexAnalyzer();//中文分词
// Analyzer analyzer = new IKAnalyzer();//中文分词
Analyzer analyzer = new IKAnalyzer();// 中文分词
// 创建索引写入配置
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 创建索引写入对象
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 创建Document对象,存储索引
Document doc = new Document();
int id = 1;
// 将字段加入到doc中
doc.add(new IntPoint("id", id));
doc.add(new StringField("title", "Spark", Field.Store.YES));
doc.add(new TextField("content", "Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎", Field.Store.YES));
doc.add(new StoredField("id", id));
// 将doc对象保存到索引库中
indexWriter.addDocument(doc);
indexWriter.commit();
// 关闭流
indexWriter.close();
long end = System.currentTimeMillis();
System.out.println("索引花费了" + (end - start) + " 毫秒");
}
删除文档
/**
* 删除文档
*
* @throws IOException
*/
@Test
public void deleteDocumentsTest() throws IOException {
// Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
// Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
// Analyzer analyzer = new ComplexAnalyzer();//中文分词
// Analyzer analyzer = new IKAnalyzer();//中文分词
System.out.println("deleteDocumentsTest");
Analyzer analyzer = new IKAnalyzer();// 中文分词
// 创建索引写入配置
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
// 创建索引写入对象
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
// 删除title中含有关键词“Spark”的文档
long count = indexWriter.deleteDocuments(new Term("title", "Spark"));
// 除此之外IndexWriter还提供了以下方法:
// DeleteDocuments(Query query):根据Query条件来删除单个或多个Document
// DeleteDocuments(Query[] queries):根据Query条件来删除单个或多个Document
// DeleteDocuments(Term term):根据Term来删除单个或多个Document
// DeleteDocuments(Term[] terms):根据Term来删除单个或多个Document
// DeleteAll():删除所有的Document
// 使用IndexWriter进行Document删除操作时,文档并不会立即被删除,而是把这个删除动作缓存起来,当IndexWriter.Commit()或IndexWriter.Close()时,删除操作才会被真正执行。
indexWriter.commit();
indexWriter.close();
System.out.println("删除完成:" + count);
}
按词条搜索
/**
* 按词条搜索
* <p>
* TermQuery是最简单、也是最常用的Query。TermQuery可以理解成为“词条搜索”,
* 在搜索引擎中最基本的搜索就是在索引中搜索某一词条,而TermQuery就是用来完成这项工作的。
* 在Lucene中词条是最基本的搜索单位,从本质上来讲一个词条其实就是一个名/值对。
* 只不过这个“名”是字段名,而“值”则表示字段中所包含的某个关键字。
*
* @throws IOException
*/
@Test
public void termQueryTest() throws IOException {
String searchField = "title";
//这是一个条件查询的api,用于添加条件
TermQuery query = new TermQuery(new Term(searchField, "Spark"));
//执行查询,并打印查询到的记录数
executeQuery(query);
}
多条件查询
/**
* 多条件查询
* <p>
* BooleanQuery也是实际开发过程中经常使用的一种Query。
* 它其实是一个组合的Query,在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。
* BooleanQuery本身来讲是一个布尔子句的容器,它提供了专门的API方法往其中添加子句,
* 并标明它们之间的关系,以下代码为BooleanQuery提供的用于添加子句的API接口:
*
* @throws IOException
*/
@Test
public void BooleanQueryTest() throws IOException {
System.out.println("BooleanQueryTest");
String searchField1 = "title";
String searchField2 = "content";
Query query1 = new TermQuery(new Term(searchField1, "Spark"));
Query query2 = new TermQuery(new Term(searchField2, "Apache"));
BooleanQuery.Builder builder = new BooleanQuery.Builder();
// BooleanClause用于表示布尔查询子句关系的类,
// 包 括:
// BooleanClause.Occur.MUST,
// BooleanClause.Occur.MUST_NOT,
// BooleanClause.Occur.SHOULD。
// 必须包含,不能包含,可以包含三种.有以下6种组合:
//
// 1.MUST和MUST:取得连个查询子句的交集。
// 2.MUST和MUST_NOT:表示查询结果中不能包含MUST_NOT所对应得查询子句的检索结果。
// 3.SHOULD与MUST_NOT:连用时,功能同MUST和MUST_NOT。
// 4.SHOULD与MUST连用时,结果为MUST子句的检索结果,但是SHOULD可影响排序。
// 5.SHOULD与SHOULD:表示“或”关系,最终检索结果为所有检索子句的并集。
// 6.MUST_NOT和MUST_NOT:无意义,检索无结果。
builder.add(query1, BooleanClause.Occur.SHOULD);
builder.add(query2, BooleanClause.Occur.SHOULD);
BooleanQuery query = builder.build();
// 执行查询,并打印查询到的记录数
executeQuery(query);
}
中文分词器
/**
* IKAnalyzer 中文分词器 SmartChineseAnalyzer smartcn分词器 需要lucene依赖 且和lucene版本同步
*
* @throws IOException
*/
@Test
public void AnalyzerTest() throws IOException {
// Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
// Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
// Analyzer analyzer = new ComplexAnalyzer();//中文分词
// Analyzer analyzer = new IKAnalyzer();//中文分词
System.out.println("AnalyzerTest");
Analyzer analyzer = null;
String text = "Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎";
analyzer = new IKAnalyzer();// IKAnalyzer 中文分词
printAnalyzerDoc(analyzer, text);
System.out.println();
analyzer = new ComplexAnalyzer();// MMSeg4j 中文分词
printAnalyzerDoc(analyzer, text);
System.out.println();
analyzer = new SmartChineseAnalyzer();// Lucene 中文分词器
printAnalyzerDoc(analyzer, text);
}
代码和内容参考
https://github.com/souyunku/SpringBootExamples/tree/master/spring-boot-lucene-demo