共计 4854 个字符,预计需要花费 13 分钟才能阅读完成。
最近在我的项目中用到了 solr,查阅材料晓得 solr 是基于 Lucene 实现了。本着刨根问底的精力,来钻研一下 Lucene
啥是 Lucene?
Lucene 是 apache 下的一个开源的全文本搜索引擎包。他为开发人员提供了一个简略 工具包,以不便在指标零碎中实现全文本搜寻的性能
为啥要用 Lucene?
有人可能要说了,不就是搜寻吗,我间接用含糊查问在数据库查问它不香吗?
其实在数据量比拟小的时候用 sql 实现搜寻性能也无大碍,然而当数据量很大的时候数据库的压力就会十分大,而且含糊查问无奈应用索引,所以必须全表查问,具体的毛病如下
- SQL 只能对数据库表进行搜寻,不能间接针对硬盘上的文件进行搜寻
- SQL 没有相关度排名
- 应用含糊查问的时候是对全表的遍历,效率低下
- 当数据库不在本地的时候,查问十分慢
所以很多的我的项目中实现搜寻性能都是应用第三方的搜寻工具,比方 solr 等。Lucene 并不是搜寻服务器,它不能独自的运行,它只是一个工具包,为用户提供一系列的 api 去调用。而很多的第三方的搜寻工具就是封装扩大了 Lucene 而实现的,所以学习 Lucene 还是很有必要滴
在理解 Lucene 的工作原理之前先谈一下搜寻的外围索引
索引是古代搜索引擎的外围,建设索引的过程就是把源数据处理成十分不便查问的索引文件的过程。为什么索引如此重要,试想一下,如果没有索引,当初你要在大量的文档中搜寻蕴含某个关键字的文档,这时因为没有索引你就必须把这些文档程序读入内存,而后顺次查看每个文档中是否蕴含相应的关键字,这个过程十分耗时,在数据量大,而且高并发的场景下,您就别在短时间内失去后果了。所以这就是建设索引的起因,能够把索引设想成一种数据结构,这种数据结构能够帮忙你疾速随机的拜访存储在索引中的关键词,通过关键词能疾速的定位到相干的文档。
全文本检索
在 Lucene 的介绍提到了全文本检索,那什么是全文本检索呢?
全文本检索就是提前将指标文档中的词提取进去,组成索引,通过查问索引定位到相干的文档。这种先建设索引再进行查问的过程就是全文本检索
Lucence 如何实现全文本检索?
Lucene 应用倒排索引实现全文本检索,它保护这一个词的表,对于表中的每一个词语都有一个链表形容了哪些文档蕴含了这个词,这样在用户搜寻的时候,找到关键词后就能够疾速失去相干文档
Lucene 实现流程
<img src=”D:java 资源后端搜寻 Lucene.jpg” style=”zoom:75%;” />
Lucene 实现全文检索的俩大流程:建设索引 + 搜寻流程
建设索引:采集数据 –> 构建文档对象(document)–> 对文档分词 –> 建设索引
搜寻流程:创立查问 —> 从索引库中查问 —> 渲染搜寻后果
理解一下 Lucene 中的相干类
Package: org.apache.lucene.document
很显著,这个包就是用来将咱们咱们要检索的数据封装为 document 对象的,该包下的 Filed 类用于阐明 document 的域,也就是 document 中会有什么属性
Package: org.apache.lucene.analysis
在建设索引之前咱们必须先进行分词操作,这个包就是对文档分词,为建设索引做筹备
Package: org.apache.lucene.index
建设索引,以及对索引进行更新
Package: org.apache.lucene.search
在建设好的索引上进行搜寻所须要的类
入门 demo
导入依赖呗
<!-- lucene 外围包 --> | |
<dependency> | |
<groupId>org.apache.lucene</groupId> | |
<artifactId>lucene-core</artifactId> | |
<version>${lucene.verseion}</version> | |
</dependency> | |
<!-- lucene 分词器,实用于英文 --> | |
<dependency> | |
<groupId>org.apache.lucene</groupId> | |
<artifactId>lucene-analyzers-common</artifactId> | |
<version>${lucene.verseion}</version> | |
</dependency> | |
<!-- lucene 中文分词器,实用于中文 --> | |
<dependency> | |
<groupId>org.apache.lucene</groupId> | |
<artifactId>lucene-analyzers-smartcn</artifactId> | |
<version>${lucene.verseion}</version> | |
</dependency> | |
<!-- 对分词索引查问解析 --> | |
<dependency> | |
<groupId>org.apache.lucene</groupId> | |
<artifactId>lucene-queryparser</artifactId> | |
<version>${lucene.verseion}</version> | |
</dependency> | |
<!-- 检索关键字高亮显示 --> | |
<dependency> | |
<groupId>org.apache.lucene</groupId> | |
<artifactId>lucene-highlighter</artifactId> | |
<version>${lucene.verseion}</version> | |
</dependency> |
先简略构建一个 entity 类用来模仿数据,这里应用了 lombok 插件
/** | |
* @author sheledon | |
*/ | |
@Setter | |
@Getter | |
@ToString | |
public class Book { | |
private Integer id; | |
private String name; | |
private Float price; | |
private String pic; | |
private String description; | |
} |
构建索引
@Test | |
public void createIndex() throws IOException {List<Book> list=new LinkedList<>(); | |
// 模仿数据 | |
for (int i=0;i<200;i++){Book b=new Book(); | |
b.setId(i); | |
b.setDescription("i"+i); | |
b.setName("java"+i*10); | |
b.setPic("中国"+i*2); | |
b.setPrice(Float.valueOf(i)); | |
list.add(b); | |
} | |
// 构建 Document 对象,将数据存入 document 对象中 | |
List<Document> documentList=new ArrayList<Document>(); | |
Document document; | |
for (Book book:list){document=new Document(); | |
// 构建 field 域,了解这一步有利于 solr 域的了解 | |
Field id=new StringField("id",book.getId().toString(), Field.Store.YES); | |
Field name=new TextField("name",book.getName(), Field.Store.YES); | |
Field price=new FloatDocValuesField("price",book.getPrice()); | |
Field pic = new StoredField("pic", book.getPic()); | |
Field description = new TextField("description", | |
book.getDescription(), Field.Store.YES); | |
// 将 field 域设置到 Document 对象中 | |
document.add(id); | |
document.add(name); | |
document.add(price); | |
document.add(pic); | |
document.add(description); | |
documentList.add(document); | |
} | |
// 构建分词器,对 document 进行分词 | |
Analyzer analyzer = new SmartChineseAnalyzer(); | |
// 配置 IndexWriter | |
IndexWriterConfig cfg = new IndexWriterConfig(analyzer); | |
// 指定索引库的地址 | |
File indexFile=new File("../"); | |
Directory directory= FSDirectory.open(indexFile.toPath()); | |
//IndexWriter 对象,负责对索引的建设,更新 | |
IndexWriter writer=new IndexWriter(directory,cfg); | |
// 分词建设索引,并且增加到索引库中 | |
for (Document doc:documentList){writer.addDocument(doc); | |
} | |
writer.close();} |
搜寻流程
@Test | |
public void indexSearch() throws ParseException, IOException { | |
// 创立 query 对象 | |
// 应用 QueryParser 搜寻时,须要指定分词器,搜寻时的分词器要和索引时的分词器统一 | |
// 第一个参数:默认搜寻的域的名称 | |
QueryParser parser=new QueryParser("name",new StandardAnalyzer()); | |
// 通过 queryParser 来创立 query 对象 | |
// 参数:输出的 lucene 的查问语句 | |
Query query=parser.parse("name:java"); | |
// 指定索引库 | |
File file=new File("../"); | |
Directory directory=FSDirectory.open(file.toPath()); | |
// 创立 IndexReader | |
IndexReader reader= DirectoryReader.open(directory); | |
IndexSearcher searcher=new IndexSearcher(reader); | |
// 通过 search 在索引库中查问 | |
TopDocs topDocs=searcher.search(query,10); | |
// 依据查问条件匹配出的记录总数 | |
long count=topDocs.totalHits; | |
System.out.println("匹配总条数:"+count); | |
// 依据查问条件匹配出的记录 | |
ScoreDoc[] scoreDocs = topDocs.scoreDocs; | |
for (ScoreDoc scoreDoc : scoreDocs) { | |
// 获取文档的 ID | |
int docId = scoreDoc.doc; | |
// 通过 ID 获取文档 | |
Document doc = searcher.doc(docId); | |
System.out.println("商品 ID:" + doc.get("id")); | |
System.out.println("商品名称:" + doc.get("name")); | |
System.out.println("商品价格:" + doc.get("price")); | |
System.out.println("商品图片地址:" + doc.get("pic")); | |
System.out.println("=========================="); | |
// System.out.println("商品形容:" + doc.get("description")); | |
} | |
// 敞开资源 | |
reader.close();} |
最初
我是不想当码农的小白,平时会写写一些技术博客,举荐优良的程序员博主给大家还有本人遇到的优良的 java 学习资源,心愿和大家一起提高,独特成长。关注对我真的很重要,老低微了!
公众号: 小白不想当码农