Lucene笔记

背景

前段时间完成的会员积分系统,该系统的积分赠送等规则相对复杂,在匹配规制的时候很难精确的筛选出想要的规制,只能在数据库简单的赛选后,再将结果通过程序进行匹配。这种实现方式当规制多的时候命中规制会特别慢。


数据库的缺陷

传统的关系型数据库是通过索引来达到快速查询的目的,但是在全文搜索的业务场景下,索引很难满足。我们先举个例子看看关系型数据库为什么无法满足全文搜索的要求。

ID地址开发商户型面积单价交通周边
1地址A万科两房一厅、三房两厅两卫、四房两厅60平、99平、160平30000、33000、36000地铁、BRT华润万家、市一级学校、购物街等
2地址B碧桂园三房两厅两卫、四房两厅、独立别墅99平、160平18000、20000公交、地铁游乐园、动物园
3地址C开发商A、开发商B三房两厅两卫、四房两厅99平、160平8000公交购物商场
  1. 用户A:天河某公司高管,有两个小孩;他的搜索条件是地址(天河)+户型(四房两厅)+周边(学位房),这些查询条件都是需要通过模糊查询进行搜索结果的;
  2. 用户B:某碧桂园员工,想买房结婚,价格希望在2万块钱左右,出行方便,喜欢游玩;他的搜索条件是碧桂园+99平+20000+游乐园;其中户型、面积、单价、交通、周边都需要模糊查询;

以上只是简单的举了两个例子,实际上搜索条件是无法列举完全的,各种排列组合特别的多,且不固定。我们按照常规方案,将这些数据存在关系数据库中,会有以下缺陷:

  • 由于搜索条件可以任意排列组合,如果通过索引来满足,则索引的数量会特别多;
  • 模糊查询只能通过 like ‘%***%’ 进行搜索结果,而这种like查询是全表扫描,不会走索引,效率非常低

由此可以看出关系数据库的全文搜索功能比较弱,只能使用like进行整表扫描匹配,性能非常低,在互联网这种搜索复杂的场景下无法满足,而全文检索可以解决这痛点。

全文检索基本原理

全文检索的技术原理是倒排索引,也可以称作反向索引。其是一种索引方法,器基本原理是建立单词到文档的索引。当然这里指的文档是一个泛指,它不单单指一些普通的文档,还可以是数据库中的某一条记录。

Lucene

介绍

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。–【百度百科】

基本概念

Lucene的索引结构从大到小分为以下几个概念:index,segments,document,field,term

  • Index:一个索引,包括所有需要的信息内容;
  • Segments:可以理解为一个子索引(sub-index),每当往index中新加入一个document时,都会新生成一个segments保存这个document,然后通过判断,合并部分segments,最后通过优化索引的命令,把所有的segments合并成一个index,Lucene 8.0.0会自动合并优化,而且也只会保留一个segments文件;
  • Document:一般以document为单位往index中添加记录,一个document可以是一个txt,一个html或者是数据库的一条记录。一个document由几个field的组成;
  • Field:一个document通常被分为几个field,用于保存不同的信息,如数据库的一条记录,不同的字段就是不同的field,当搜索时,可以指定在哪个field进行搜索,field由一组term组成;
  • Term:是最基本的索引单位,一般每个field由很多个term组成;term由一对值组成,一个值表示他属于哪个field,另一个表示他本身的值,所以两个不同的field里相同的string并不是一个term。

Lucene过程

在这里插入图片描述
1、生成文档;
2、通过解析器进行解析文本,创建词条;
3、创建词条与文档的索引关系;
4、打开索引文件;
5、通过解析器解析用户输入的查询条件;
6、检索并返回结果。

代码实现

生成索引

以下代码基于Lucene 8.0.0

package com.vip.fcs.vpos.Lucene;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class IndexFiles {
	public static void main(String[] args) {
		String indexPath = "E:\\Lucene\\index";

		try {
			Path file = Paths.get(indexPath);

			IndexWriter writer = getIndexWriter(file);

			addDocument(file,writer);

			writer.close();

		} catch (IOException e) {
			System.out.println(" caught a " + e.getClass() +
					"\n with message: " + e.getMessage());
		}
	}

	static IndexWriter getIndexWriter(Path file) throws IOException {
		Directory dir = FSDirectory.open(file);
		Analyzer analyzer = new StandardAnalyzer();
		IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
		IndexWriter writer = new IndexWriter(dir, iwc);
		return writer;
	}

	static void addDocument(Path file,IndexWriter writer) throws IOException {
		Document doc1 = new Document();
		doc1.add(new StringField("id", "1", Field.Store.YES));
		doc1.add(new StringField("address", "地址A",Field.Store.NO));
		doc1.add(new TextField("other", "万科,两房一厅、三房两厅两卫、四房两厅,60平、99平、160平,地铁、BRT",Field.Store.NO));
		writer.addDocument(doc1);

		Document doc2 = new Document();
		doc2.add(new StringField("id", "2", Field.Store.YES));
		doc2.add(new StringField("address", "地址B",Field.Store.NO));
		doc2.add(new TextField("other", "碧桂园,三房两厅两卫、四房两厅、独立别墅,游乐园、动物园",Field.Store.NO));
		writer.addDocument(doc2);

		Document doc3 = new Document();
		doc3.add(new StringField("id", "3", Field.Store.YES));
		doc3.add(new StringField("address", "地址C",Field.Store.NO));
		doc3.add(new TextField("other", "开发商A、开发商B,三房两厅两卫、四房两厅,购物商场",Field.Store.NO));
		writer.addDocument(doc3);
	}
}

全局搜索

package com.vip.fcs.vpos.lucene;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.QueryParser;
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.FSDirectory;

import java.io.IOException;
import java.nio.file.Paths;

public class SearchFiles {
	private SearchFiles() {}

	public static void main(String[] args) throws Exception {

		String index = "E:\\Lucene\\index";
		String field = "other";
		String queryString = "三房两厅两卫";
		int hitsPerPage = 10;


		IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(index)));
		IndexSearcher searcher = new IndexSearcher(reader);

		Analyzer analyzer = new StandardAnalyzer();
		QueryParser parser = new QueryParser(field, analyzer);
		Query query = parser.parse(queryString.trim());
		System.out.println("Searching for: " + query.toString(field));

		doPagingSearch(searcher, query, hitsPerPage);

		reader.close();
	}

	public static void doPagingSearch( IndexSearcher searcher, Query query,
			int hitsPerPage) throws IOException {

		TopDocs results = searcher.search(query, 5 * hitsPerPage);
		ScoreDoc[] hits = results.scoreDocs;
		for(int i = 0;i < hits.length;i++){
			Document doc = searcher.doc(hits[i].doc);
			String id = doc.get("id");
			String address = doc.get("address");
			String other = doc.get("other");
			System.out.println("id : " + id);
			System.out.println("address : " + address);
			System.out.println("other : " + other);
			System.out.println("------------------------------------------------");
		}
	}
}

通过“三房两厅两卫”搜索出来的结果如下:

Searching for: 三 房 两 厅 两 卫
id : 1
address : null
other : null
------------------------------------------------
id : 3
address : null
other : null
------------------------------------------------
id : 2
address : null
other : null
------------------------------------------------

其中address 、other这两个Field设置的Store是Field.Store.NO,表示不存储,故这里返回null

关键类说明

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

Analyzer:分析器,也叫分词器。解析各种文本,然后将文本拆分为一个个词条;如果Lucene提供的分词器不能满足需求的时候可以实现Analyzer类里面的createComponents抽象方法,而实现自己的Analyzer

Directory:索引存放的位置;lucene 提供了两种索引存放的位置,一种是磁盘(FSDirectory),一种是内存(RAMDirectory),一般情况下都会选择存放在磁盘进行持久化。

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

Field:字段,可以选择是否需要进行分词,是否需要存储。

IndexSearcher:基本的检索工具类。

Query:查询类,具体实现类有:精准匹配TermQuery,区间查询TermRangeQuery,数字搜索NumericRangeQuery等一些类。

QueryParser:对查询条件通过分词器进行解析,然后在组装成Query。

Hits:通过查询条件命中结果集合。

索引文件查看

通过Lucene的索引查看工具“luke”,可以很清楚的看到Lucene生成的索引文件长什么样。
注意:luke的版本必须和Lucene的一致,要不然无法打开索引文件

在这里插入图片描述
在这里插入图片描述
可以看到id、address没有进行分词,而other则按照字进行了分词。

下载地址

luke:https://github.com/DmitryKey/luke
Lucene:https://lucene.apache.org/

作者:张伟峰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值