最近在项目中用到了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核心技术卷关键字领取《java核心技术卷》pdf
回复jvm领取《深入理解Java虚拟机》pdf和《自己动手写jvm》
回复设计模式领取《headfirst设计模式》pdf
回复计算机网络领取《计算机网络自顶向下》pdf
最后
我是不想当码农的小白,平时会写写一些技术博客,推荐优秀的程序员博主给大家还有自己遇到的优秀的java学习资源,希望和大家一起进步,共同成长。关注对我真的很重要,老卑微了!
公众号: 小白不想当码农
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。