Lucene 是什么
Lucene 是一款优秀的全文搜索框架,它可以在系统中对文本进行快速检索,达到加速查询的目的。
如图:这是在我们没有Lucene 全文索引下对数据查询的常用方案
这种在用户量小,数据量小的时候我们使用这方方案是没有任何问题的,但是面对百万级甚至千万级的数据怎么办呢,显然这种是存在明显弊端的,在使用like进行全文检索的时候,会导致我们的索引失效,进行全表扫描。
下图是使用Lucene 之后的整体架构方案
可以把大批量数据放入Lucene 索引库,查询的时候不直接走我们的数据库全表like扫描,直接走我们的索引库即可。
Lucene 数据查询方法
一般我们全文检索有两种方案:
- 顺序扫描法
顺序扫描法即丛文档头到文档的末尾,如果多个存在多个文档,则顺序遍历每一个文档,逐行扫描,直到所有文件都扫描完毕进行结果汇总返回,这种方案的优点就是查询的准确度非常高,但是随着文档的增大或者个数增加,查询的速度会有一定的变慢。 - 倒排索引
倒排索引就类似于我们的新华词典,新华词典的目录就相当于我们Lucene 中的索引,查询内容偏旁部首,然后快速定位文档,在Lucene 当中,我们会对文档进行数据拆分,形成关键字当作数据的索引,在进行查询的时候,我们使用关键字可直接定位到文档内容。
顺序扫描法和倒排索引的比较:
倒排索引是在数据查询之前建立,在查询的时候可以直接通过关键词定位到文档内容,而顺序扫描法需要我们顺序遍历每一个文档,在数据量大的时候肯定比倒排索引要耗时,但是倒排索引需要我们占用额外的物理空间去存放我们的索引数据(关键词),其实说白了,在计算机的世界中,时间和空间永远是一个相互矛盾的存在,二者权衡利弊,需要我们根据自己的机器性能和业务选择更合适的技术方案才是最正确的。
Lucene 使用场景
- 专业搜索引擎公司 (百度,google搜索)
- 站内搜索(京东,淘宝,贴吧等等等)
Lucene 索引创建流程
- 从数据库或者文档中获取数据(from db or document)
- 构建文档对象(document)
- 创建分词器对象
- 创建IndexWriterConfig配置信息类
- 创建Directory对象,声明索引库存储位置
- 创建IndexWriter写入对象
- 把Document写入到索引库中
- 释放资源
@Test
public void createLuceneIndexFromDbData() throws Exception {
//1.查询所有商品信息
List<Product> products= ProductMapper.queryAllList();
List<Document> documents = null;
if(CollectionUtils.isNotEmpty(products)){
documents = new ArrayList<>(products.size());
}
products.foreach(p ->{
//2.创建文档对象document
Document document = new Document();
document.add(StringField("id",p.getId(),Field.Store.YES));
document.add(new TextField("productName",p.getProductName(),Field.Store.YES));
document.add(new FloatPoint("price", p.getPrice()));
document.add(new StoredField("price",p.getPrice()));
//...
documents.add(document);
});
//3.创建分词器
Analyzer analyzer = new IKAnalyzer();
//4.创建Directory对象,声明索引库的位置
Directory directory = FSDirectory.open(Paths.get("E:\\dir"));
//5.创建IndexWriteConfig对象,写入索引需要的配置
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//6.创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//7.写入到索引库,通过IndexWriter添加文档对象document
for (Document doc : documents) {
indexWriter.addDocument(doc);
}
// 8.释放资源
indexWriter.close();
}
Lucene索引查询流程
- 创建分词器
- 创建query搜索对象
- 创建Directory流对象,声明索引库位置
- 创建索引读取对象IndexReader
- 创建索引搜索对象IndexSearcher
- 使用搜索对象,执行搜索,返回结果集topDocs
- 解析结果集
- 释放资源
@Test
public void testQueryByLucene ()throws Exception{
//1.创建分词器
Analyzer analyzer = new IKAnalyzer();
//2.创建query搜索对象
QueryParser queryParser = new QueryParser("productName",analyzer);
Query query = queryParser.parse("一加手机");
//3. 创建Directory流对象,声明索引库位置
Directory directory = FSDirectory.open(Paths.get("E:\\dir"));
//4.创建索引读取对象
IndexReader reader = DirectoryReader.open(directory);
//5.创建索引搜索对象IndexSearcher
IndexSearcher searcher = new IndexSearcher(reader);
//6.使用搜索对象,执行搜索,返回结果集topDocs,10是只显示前10条记录
TopDocs topDocs = searcher.search(query, 10);
//7.解析结果集
topDocs.scoreDocs
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
int docID = scoreDoc.doc;
Document doc = searcher.doc(docID);
System.out.println("*-------*");
System.out.println("id:" + doc.get("id"));
System.out.println("productName:" + doc.get("productName"));
System.out.println("price:" + doc.get("price"));
}
reader.close();
}
Lucene Field域
Field是文档Document组成的载体,一个document下可以包含多个Field,Field包括FieldName和FieldValue两部分,filedValue即是要索引部分。
- 是否分词
定义field的value是否可以被分词进行索引 - 是否索引
定义field的value是否可以被索引,在不被分词的情况下也可以进行索引 - 是否存储
定义field中的value是否可以存储在document文档中,如果保存则可以进行查询,不保存不可以进行查询。
Field常用类型
具体实现 | type(类型) | 是否分词 | 是否索引 | 是否存储 | 描述 |
---|---|---|---|---|---|
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y/N | 用于构建字符串,但是不会进行分词,会将整个字符串存储到索引中,比如订单号,身份证号等信息,是否存储在文档由Store.YES/Store.NO来决定。 |
FloatPoint(FieldName, FieldValue) | Float型 | Y | Y | N | 这个Field用来构建一个Float数字型Field,进行分词和索引,不存储比如(价格) 存储在文档中。 |
DoublePoint(FieldName, FieldValue) | Double 型 | Y | Y | N | 同FloatPoint |
LongPoint(FieldName, FieldValue) | Long型 | Y | Y | N | 同上 |
IntPoint(FieldName, FieldValue) | Integer型 | Y | Y | N | 同上 |
StoredField(FieldName, FieldValue) | 重载方法,配合其它类型一起使用 | N | N | Y | 用来构建feild,不分词,不索引,配合其它类型来实现存储。 |
TextField(FieldName, FieldValue,Store.NO) 或 TextField(FieldName,reader) | 字符串/IO流 | Y | Y | Y/N | 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略。 |
NumericDocValuesField(FieldName, FieldValue) | 数值 | - | - | - | 配合其他域排序 。 |
Lucene 中的分词器
- StandardAnalyzer
对英文支持友好,对中文只是单子拆分,没有语义分析。
- WhitespaceAnalyzer
只去除文档中的空格,没有任何其它操作,不支持中文。
- SimpleAnalyzer
按照非字母切分,非字母的都会被去掉。
- CJKAnalyzer
支持中日韩文字,前三个字母也就是这三个国家的缩写。对中文是二分法分词, 去掉空格, 去掉标点符号。
- IK-analyzer
对中文有很好的支持,支持语义分析,也支持禁用词典和特展词词典,很灵活,推荐使用,性能小于其它分词器。