全文检索的概念
我们生活中的数据总体分为两种:结构化数据和非结构化数据。
- 结构化数据:指具有固定格式或有限长度的数据,如关系数据库等。
- 非结构化数据:指不定长或无固定格式的数据,如邮件、文档等。
非结构化数据又称为全文数据。
按照数据的分类,搜索也分为两种:
- 对结构化数据的搜索:如数据库的搜索,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) | 字符串 | N | Y | Y/N | 字符串类型Field, 不分词, 作为一个整体进行索引 (如: 身份证号, 订单编号), 是否需要存储由Store.YES或Store.NO决定 |
LongField(FieldName, FieldValue, Store.YES) IntField(FieldName, FieldValue, Store.YES) DoubleField(FieldName, FieldValue, Store.YES) | 数值型代表 | Y | Y | Y/N | Long数值型Field代表, 分词并且索引(如: 价格), 是否需要存储由Store.YES或Store.NO决定 |
StoredField(FieldName, FieldValue) | 重载方法, 支持多种类型 | N | N | Y | 构建不同类型的Field, 不分词, 不索引, 要存储. (如: 商品图片路径) |
TextField(FieldName, FieldValue, Store.NO) | 文本类型 | Y | Y | Y/N | 文本类型Field, 分词并且索引,
|
-
是否分词(tokenized)
是:作分词处理,即将Field值进行分词,分词的目的是为了索引。
否:不作分词处理。
-
是否索引(indexed)
是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域。
否:不索引,即不进行搜索。
-
是否存储(stored)
是:将Field值存储在文档域中,可以从Document中获取。
否:不存储Field值。