玩转搜索(一)-- 全文检索与Lucene实现

全文检索的概念

我们生活中的数据总体分为两种:结构化数据和非结构化数据。

  • 结构化数据:指具有固定格式或有限长度的数据,如关系数据库等。
  • 非结构化数据:指不定长或无固定格式的数据,如邮件、文档等。

非结构化数据又称为全文数据。

按照数据的分类,搜索也分为两种:

  • 对结构化数据的搜索:如数据库的搜索,windows的搜索。
  • 对非结构化数据的搜索:如利用搜索引擎搜索大量内容。

对非结构化数据也即全文数据的搜索主要有两种方法:顺序扫描法和反向索引法。

  • 顺序扫描法:顺序的扫描每个文档内容,看看是否有要搜索的关键字,实现查找文档的功能,也就是根据文档找词。
  • 反向索引法:就是提前将搜索的关键字建成索引,然后再根据索引查找文档,也就是根据词找文档。

这种先建立索引再对索引进行搜索文档的过程就叫全文检索(Full-text Search)。

全文检索技术

主流的java全文检索技术主要有:Lucene、Solr、ElasticSearch(ES)。

  • Lucene:是一个开源的全文检索引擎工具包,但它不是一个完整的全文检索引擎,需要使用者二次开发。如果使用该技术实现,需要对Lucene的API和底层原理非常了解,而且需要编写大量的java代码。
  • Solr:是一个基于Lucene实现的搜索应用服务器,可以使用rest方式的http请求,进行远程API的调用。
  • ElasticSearch:是一个基于Lucene的搜索服务器,可以使用rest方式的http请求,进行远程API的调用。

ElasticSearch与Solr的比较总结

  • 两者安装都很简单;
  • Solr利用Zookeeper进行分布式管理,而ES自身带有分布式协调管理功能;
  • Solr支持更多格式数据,而ES只支持json格式;
  • Solr官方提供的功能更多,而ES本身更注重核心功能,高级功能由很多第三方插件提供;
  • Solr在传统的搜索应用中表现较好,而ES在处理实时搜索时效率更好。

Solr是传统搜索应用的有力解决方案,但ES更适用于新兴的实时搜索应用。

全文检索的流程分析

主要分为两大流程:创建索引、搜索索引

  • 创建索引:将数据提取信息,创建索引的过程。
  • 搜索索引:根据查询请求,搜索创建的索引,返回结果的过程。

创建索引流程

获得文档

获取需要搜索的原始信息,这个过程就是信息采集。采集数据的目的是为了将原始内容存除到Document对象中。

创建文档

目的是统一数据格式(Document),方便文档分析。一个Document文档中包括多个Field域,Field中存储内容。

分析文档

对Field域进行分析。主要通过分词组件(Tokenizer)和语言处理组件(Linguistic Processor)完成。最终得到的结果为词(Term),Term也是索引库的最小单位。

索引文档

将Term传为给索引组件(Indexer)。Indexer主要实现创建Term字典,排序Term字典,合并Term字典。

搜索索引流程

查询语句格式如下:

域名:关键字,比如name:hello
域名:[min TO max],比如price:[100 TO 200]

多个查询语句之前,使用关键字AND、OR、NOT表示逻辑关系。

词法分析

最终得到搜索关键字列表。

语法分析

得到每个关键字之前的逻辑关联。

搜索索引

在索引表中,根据关键字查出文档链表。对文档链表进行逻辑合并计算操作。

排序

根据相关性对结果进行排序。影响相关度打分的因素:

  • Term Frequency(tf):tf越高,权重越高。
  • Document Frequency(df):df越高,权重越低。
  • 人为设置Field的Boost加权值。

Lucene的Java代码实现

创建索引

		// 1. 数据采集:模拟一个Item列表,包括id、name、price、pic、description这些字段
		List<Item> itemList = itemService.queryItemList();

		// 2. 创建Document文档对象
		List<Document> documents = new ArrayList<>();
		for (Item item : itemList) {
			Document document = new Document();

			// Document文档中添加Field域
			// 商品Id
			// Store.YES:表示存储到文档域中
			document.add(new StoredField("id", item.getId()));
			// 商品名称
			document.add(new TextField("name", item.getName(), Store.YES));
			// 商品价格
			document.add(new DoubleField("price", item.getPrice(), Store.YES));
			// 商品图片地址
			document.add(new StoredField("pic", item.getPic()));
			// 商品描述
			document.add(new TextField("description", item.getDescription(), Store.NO));

			// 把Document放到list中
			documents.add(document);
		}

		// 指定分词器:IKAnalyzer为中文分词器
		Analyzer analyzer = new IKAnalyzer();
		// 配置文件
		IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);

		// 指定索引库路径
		String indexPath = "D:\\lucene\\";
		// 指定索引库对象
		Directory dir = FSDirectory.open(new File(indexPath));
		// 创建索引写对象
		// 3. 分析索引并创建索引
		IndexWriter writer = new IndexWriter(dir, iwc);
		writer.addDocuments(documents);

		// 释放资源
		writer.close();

搜索索引

		// 指定索引库路径
		String indexPath = "D:\\lucene\\";
		// 指定索引库对象
		Directory dir = FSDirectory.open(new File(indexPath));

		// 索引读对象
		IndexReader reader = DirectoryReader.open(dir);
		// 索引搜索器
		IndexSearcher searcher = new IndexSearcher(reader);

		// Analyzer analyzer = new StandardAnalyzer();
		Analyzer analyzer = new IKAnalyzer();
		// 通过QueryParser解析查询语法,获取Query对象
		QueryParser parser = new QueryParser("name", analyzer);
		// 参数是查询语法
		Query query = parser.parse("name:华为手机");
		TopDocs topDocs = searcher.search(query, 100);

		ScoreDoc[] scoreDocs = topDocs.scoreDocs;

		for (ScoreDoc scoreDoc : scoreDocs) {
			Document document = searcher.doc(scoreDoc.doc);
			System.out.println("name : " + document.get("name"));
		}

		reader.close();

Field域分析

Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

Field类型数据类型是否分词是否索引是否存储说明
StringField(FieldName, FieldValue, Store.YES)字符串NYY/N字符串类型Field, 不分词, 作为一个整体进行索引
(如: 身份证号, 订单编号),
是否需要存储由Store.YES或Store.NO决定

LongField(FieldName, FieldValue, Store.YES)

IntField(FieldName, FieldValue, Store.YES)

DoubleField(FieldName, FieldValue, Store.YES)

数值型代表YYY/NLong数值型Field代表, 分词并且索引(如: 价格),
是否需要存储由Store.YES或Store.NO决定
StoredField(FieldName, FieldValue)重载方法, 支持多种类型NNY构建不同类型的Field, 不分词, 不索引, 要存储.
(如: 商品图片路径)
TextField(FieldName, FieldValue, Store.NO)文本类型YYY/N

文本类型Field, 分词并且索引,
是否需要存储由Store.YES或Store.NO决定

 

  • 是否分词(tokenized)

    是:作分词处理,即将Field值进行分词,分词的目的是为了索引。

    否:不作分词处理。

  • 是否索引(indexed)

    是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域。

    否:不索引,即不进行搜索。

  • 是否存储(stored)

    是:将Field值存储在文档域中,可以从Document中获取。

    否:不存储Field值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值