Spring Boot 中使用 Java API 调用 lucene

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础创建起完整的全文检索引擎html

全文检索概述

好比,咱们一个文件夹中,或者一个磁盘中有不少的文件,记事本、world、Excel、pdf,咱们想根据其中的关键词搜索包含的文件。例如,咱们输入Lucene,全部内容含有Lucene的文件就会被检查出来。这就是所谓的全文检索。java

所以,很容易的咱们想到,应该创建一个关键字与文件的相关映射,盗用ppt中的一张图,很明白的解释了这种映射如何实现。git

倒排索引github

倒排索引

有了这种映射关系,咱们就来看看Lucene的架构设计。 下面是Lucene的资料必出现的一张图,但也是其精髓的归纳。spring

倒排

咱们能够看到,Lucene的使用主要体如今两个步骤:apache

1 建立索引,经过IndexWriter对不一样的文件进行索引的建立,并将其保存在索引相关文件存储的位置中。api

2 经过索引查寻关键字相关文档。缓存

在Lucene中,就是使用这种“倒排索引”的技术,来实现相关映射。bash

Lucene数学模型

文档、域、词元架构

文档是Lucene搜索和索引的原子单位,文档为包含一个或者多个域的容器,而域则是依次包含“真正的”被搜索的内容,域值经过分词技术处理,获得多个词元。

For Example,一篇小说(斗破苍穹)信息能够称为一个文档,小说信息又包含多个域,例如:标题(斗破苍穹)、做者、简介、最后更新时间等等,对标题这个域采用分词技术又能够获得一个或者多个词元(斗、破、苍、穹)。

Lucene文件结构

层次结构

index 一个索引存放在一个目录中

segment 一个索引中能够有多个段,段与段之间是独立的,添加新的文档可能产生新段,不一样的段能够合并成一个新段

document 文档是建立索引的基本单位,不一样的文档保存在不一样的段中,一个段能够包含多个文档

field 域,一个文档包含不一样类型的信息,能够拆分开索引

term 词,索引的最小单位,是通过词法分析和语言处理后的数据。

正向信息

按照层次依次保存了从索引到词的包含关系:index-->segment-->document-->field-->term。

反向信息

反向信息保存了词典的倒排表映射:term-->document

IndexWriter lucene中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程当中的一些参数使用。

Analyzer 分析器,主要用于分析搜索引擎遇到的各类文本。经常使用的有StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。

Directory 索引存放的位置;lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。通常状况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。

Document 文档;Document至关于一个要进行索引的单元,任何能够想要被索引的文件都必须转化为Document对象才能进行索引。

Field 字段。

IndexSearcher 是lucene中最基本的检索工具,全部的检索都会用到IndexSearcher工具;

Query 查询,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,若有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。

QueryParser 是一个解析用户输入的工具,能够经过扫描用户输入的字符串,生成Query对象。

Hits 在搜索完成以后,须要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在lucene中,搜索的结果的集合是用Hits类的实例来表示的。

测试用例

Github 代码

代码我已放到 Github ,导入spring-boot-lucene-demo 项目

github github.com/souyunku/sp…

添加依赖

<!--对分词索引查询解析-->
<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-queryparser</artifactId>
	<version>7.1.0</version>
</dependency>

<!--高亮 -->
<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-highlighter</artifactId>
	<version>7.1.0</version>
</dependency>

<!--smartcn 中文分词器 SmartChineseAnalyzer smartcn分词器 须要lucene依赖 且和lucene版本同步-->
<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-analyzers-smartcn</artifactId>
	<version>7.1.0</version>
</dependency>

<!--ik-analyzer 中文分词器-->
<dependency>
	<groupId>cn.bestwu</groupId>
	<artifactId>ik-analyzers</artifactId>
	<version>5.1.0</version>
</dependency>

<!--MMSeg4j 分词器-->
<dependency>
	<groupId>com.chenlb.mmseg4j</groupId>
	<artifactId>mmseg4j-solr</artifactId>
	<version>2.4.0</version>
	<exclusions>
		<exclusion>
			<groupId>org.apache.solr</groupId>
			<artifactId>solr-core</artifactId>
		</exclusion>
	</exclusions>
</dependency>
复制代码

配置 lucene

private Directory directory;

private IndexReader indexReader;

private IndexSearcher indexSearcher;

@Before
public void setUp() throws IOException {
	//索引存放的位置,设置在当前目录中
	directory = FSDirectory.open(Paths.get("indexDir/"));

	//建立索引的读取器
	indexReader = DirectoryReader.open(directory);

	//建立一个索引的查找器,来检索索引库
	indexSearcher = new IndexSearcher(indexReader);
}

@After
public void tearDown() throws Exception {
	indexReader.close();
}

**
 * 执行查询,并打印查询到的记录数
 *
 * @param query
 * @throws IOException
 */
public void executeQuery(Query query) throws IOException {

	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 {

	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();
	}
}
	
复制代码

建立索引

@Test
public void indexWriterTest() throws IOException {
	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) + " 毫秒");
}
复制代码

响应

17:58:14.655 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 加载扩展词典:ext.dic
17:58:14.660 [main] DEBUG org.wltea.analyzer.dic.Dictionary - 加载扩展中止词典:stopword.dic
索引花费了879 毫秒
复制代码

删除文档

@Test
public void deleteDocumentsTest() throws IOException {
	//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);

	// 删除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);
}

复制代码

响应

删除完成:1
复制代码

更新文档

/** * 测试更新 * 实际上就是删除后新增一条 * * @throws IOException */
@Test
public void updateDocumentTest() throws IOException {
	//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 doc = new Document();

	int id = 1;

	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));

	long count = indexWriter.updateDocument(new Term("id", "1"), doc);
	System.out.println("更新文档:" + count);
	indexWriter.close();
}
复制代码

响应

更新文档:1
复制代码

按词条搜索

/** * 按词条搜索 * <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);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

多条件查询

/** * 多条件查询 * * BooleanQuery也是实际开发过程当中常用的一种Query。 * 它实际上是一个组合的Query,在使用时能够把各类Query对象添加进去并标明它们之间的逻辑关系。 * BooleanQuery自己来说是一个布尔子句的容器,它提供了专门的API方法往其中添加子句, * 并标明它们之间的关系,如下代码为BooleanQuery提供的用于添加子句的API接口: * * @throws IOException */
@Test
public void BooleanQueryTest() throws IOException {

	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);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

匹配前缀

/** * 匹配前缀 * <p> * PrefixQuery用于匹配其索引开始以指定的字符串的文档。就是文档中存在xxx% * <p> * * @throws IOException */
@Test
public void prefixQueryTest() throws IOException {
	String searchField = "title";
	Term term = new Term(searchField, "Spar");
	Query query = new PrefixQuery(term);

	//执行查询,并打印查询到的记录数
	executeQuery(query);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

短语搜索

/** * 短语搜索 * <p> * 所谓PhraseQuery,就是经过短语来检索,好比我想查“big car”这个短语, * 那么若是待匹配的document的指定项里包含了"big car"这个短语, * 这个document就算匹配成功。可若是待匹配的句子里包含的是“big black car”, * 那么就没法匹配成功了,若是也想让这个匹配,就须要设定slop, * 先给出slop的概念:slop是指两个项的位置之间容许的最大间隔距离 * * @throws IOException */
@Test
public void phraseQueryTest() throws IOException {

	String searchField = "content";
	String query1 = "apache";
	String query2 = "spark";

	PhraseQuery.Builder builder = new PhraseQuery.Builder();
	builder.add(new Term(searchField, query1));
	builder.add(new Term(searchField, query2));
	builder.setSlop(0);
	PhraseQuery phraseQuery = builder.build();

	//执行查询,并打印查询到的记录数
	executeQuery(phraseQuery);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

相近词语搜索

/** * 相近词语搜索 * <p> * FuzzyQuery是一种模糊查询,它能够简单地识别两个相近的词语。 * * @throws IOException */
@Test
public void fuzzyQueryTest() throws IOException {

	String searchField = "content";
	Term t = new Term(searchField, "大规模");
	Query query = new FuzzyQuery(t);

	//执行查询,并打印查询到的记录数
	executeQuery(query);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

通配符搜索

/** * 通配符搜索 * <p> * Lucene也提供了通配符的查询,这就是WildcardQuery。 * 通配符“?”表明1个字符,而“*”则表明0至多个字符。 * * @throws IOException */
@Test
public void wildcardQueryTest() throws IOException {
	String searchField = "content";
	Term term = new Term(searchField, "大*规模");
	Query query = new WildcardQuery(term);

	//执行查询,并打印查询到的记录数
	executeQuery(query);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

分词查询

/** * 分词查询 * * @throws IOException * @throws ParseException */
@Test
public void queryParserTest() throws IOException, ParseException {
	//Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
	//Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
	//Analyzer analyzer = new ComplexAnalyzer();//中文分词
	//Analyzer analyzer = new IKAnalyzer();//中文分词

	Analyzer analyzer = new IKAnalyzer();//中文分词

	String searchField = "content";

	//指定搜索字段和分析器
	QueryParser parser = new QueryParser(searchField, analyzer);

	//用户输入内容
	Query query = parser.parse("计算引擎");

	//执行查询,并打印查询到的记录数
	executeQuery(query);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

多个 Field 分词查询

/** * 多个 Field 分词查询 * * @throws IOException * @throws ParseException */
@Test
public void multiFieldQueryParserTest() throws IOException, ParseException {
	//Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
	//Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
	//Analyzer analyzer = new ComplexAnalyzer();//中文分词
	//Analyzer analyzer = new IKAnalyzer();//中文分词

	Analyzer analyzer = new IKAnalyzer();//中文分词

	String[] filedStr = new String[]{"title", "content"};

	//指定搜索字段和分析器
	QueryParser queryParser = new MultiFieldQueryParser(filedStr, analyzer);

	//用户输入内容
	Query query = queryParser.parse("Spark");

	//执行查询,并打印查询到的记录数
	executeQuery(query);
}
复制代码

响应

总共查询到1个文档
id:1
title:Spark
content:Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎!
复制代码

中文分词器

/** * 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();//中文分词

	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);
}
复制代码

三种分词响应

apache
spark
专为
大规模
规模
模数
数据处理
数据
处理
而设
设计
快速
通用
计算
引擎
复制代码
apache
spark
是
专为
大规模
数据处理
而
设计
的
快速
通用
的
计算
引擎
复制代码
apach
spark
是
专
为
大规模
数据
处理
而
设计
的
快速
通用
的
计算
引擎
复制代码

高亮处理

/** * 高亮处理 * * @throws IOException */
@Test
public void HighlighterTest() throws IOException, ParseException, InvalidTokenOffsetsException {
	//Analyzer analyzer = new StandardAnalyzer(); // 标准分词器,适用于英文
	//Analyzer analyzer = new SmartChineseAnalyzer();//中文分词
	//Analyzer analyzer = new ComplexAnalyzer();//中文分词
	//Analyzer analyzer = new IKAnalyzer();//中文分词

	Analyzer analyzer = new IKAnalyzer();//中文分词

	String searchField = "content";
	String text = "Apache Spark 大规模数据处理";

	//指定搜索字段和分析器
	QueryParser parser = new QueryParser(searchField, analyzer);

	//用户输入内容
	Query query = parser.parse(text);

	TopDocs topDocs = indexSearcher.search(query, 100);

	// 关键字高亮显示的html标签,须要导入lucene-highlighter-xxx.jar
	SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
	Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));

	for (ScoreDoc scoreDoc : topDocs.scoreDocs) {

		//取得对应的文档对象
		Document document = indexSearcher.doc(scoreDoc.doc);

		// 内容增长高亮显示
		TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(document.get("content")));
		String content = highlighter.getBestFragment(tokenStream, document.get("content"));

		System.out.println(content);
	}

}
复制代码

响应

<span style='color:red'>Apache</span> <span style='color:red'>Spark</span> 是专为<span style='color:red'>大规模数据处理</span>而设计的快速通用的计算引擎!
复制代码

代码我已放到 Github ,导入spring-boot-lucene-demo 项目

github github.com/souyunku/sp…

Contact

  • 做者:鹏磊
  • 出处:www.ymq.io
  • Email:admin@souyunku.com
  • 版权归做者全部,转载请注明出处
  • Wechat:关注公众号,搜云库,专一于开发技术的研究与知识分享

关注公众号-搜云库
搜云库
相关文章
相关标签/搜索