全文检索索引文件的结构
- 分词:将一段,一句文本信息,计算整理成片段的过程叫做分词
例如:中华人民共和国在2019年举国欢庆
中华,人民,共和国,中华人品共和国,欢庆,举国欢庆 - 词项:分词计算之后的每个片段都是一个词项 (term)
- 文档(document):将源数据整理成的索引文件中的单位数据结构,可以根据业务数据,去整理大量的文档对象用来存储数据源信息.
- 域(field):每个document封装数据的基本字段,根据业务数据灵活拼接的.
倒排索引的计算步骤
源数据:网页
国庆大阅兵,尽显中华雄风
10月1日在北京举行的国庆70周年阅兵备受各方关注
图片1http://image.jt.com/**1
出版社:新华网
- 封装对象document
id:1
title:国庆大阅兵,尽显中华雄风
content:10月1日在北京举行的国庆70周年阅兵备受各方关注
image:http://image.jt.com/**
publisher:出版社:新华网 - 计算分词
对应每个不同的document对象中属性和文本值要进行分词计算(包含着大量的参数属性,例如,文档id,域名称,偏移量,位移,频率等)
国庆(1,title),阅兵(1,title),中华(1,title) 10(1,content) 北京(1,content),国庆 (1,content),阅兵(1,content) -
分词合并 国庆([1,2],[title,content]),阅兵
(1,[title,content]),北京
([1,2],content),出版社
([1,2],publisher) - 将document和分词合并输出到索引文件中
分词器
- SimpleAnalyzer 简单分词器
- StandardAnalyzer 标准分词器
- SmartChineseAnalyzer 智能中文分词器
- 常用的中文分词器IKAnalyzer,支持扩展和停用
分词器测试
pom导入依赖
IK分析器依赖,在本地文件中,用scope和systemPath导入,交给maven管理
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>ik-analyzer</artifactId>
<version>u6</version>
<scope>system</scope>
<systemPath>D:\IKAnalyzer2012_u6.jar</systemPath>
</dependency>
<dependency> <!-- 查询相关jar包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- lucene自带只能中文分词器jar包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- 测试用到的lucene工具包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>6.0.0</version>
</dependency>
<dependency> <!-- 测试用到的lucene核心包 -->
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>6.0.0</version>
</dependency>
分词器
public class AnalyzerTest {
/*处理分词器的分词过程
* 打印阐释文本属性 词项
*/
public void printTerm(Analyzer analyzer,String msg) throws IOException{
//获取字符串流
StringReader reader=new StringReader(msg);
//可以通过传递的analyzer对象调用方法
//不同实现类底层分词逻辑不同
TokenStream tokenStream=analyzer.tokenStream("test",reader);
//fieldName:域属性名称,分词器测试没有具体意义
//将分词的文本属性打印此项的词,需要指针重置
tokenStream.reset();
CharTermAttribute attribute=tokenStream.getAttribute(CharTermAttribute.class);
while(tokenStream.incrementToken()){
System.out.println(attribute.toString());
//词项
}
}
//实现测试方法,传递使用不同的分词器
@Test
public void run() throws IOException{
//准备一句计算分词的文本
String msg="你喜欢 王者荣耀,还是 英雄联盟";
//使用不同的分词器实现类
Analyzer a1=new SimpleAnalyzer();//根据标点切分
Analyzer a2=new WhitespaceAnalyzer();//根据空格,句,字,词
Analyzer a3=new StandardAnalyzer();//根据字符切分
Analyzer a4=new SmartChineseAnalyzer();//根据中文词进行切分
Analyzer a5=new IKAnalyzer6x();
System.out.println("***********简单*************");
printTerm(a1,msg);
System.out.println("***********空格*************");
printTerm(a2,msg);
System.out.println("***********标准*************");
printTerm(a3,msg);
System.out.println("***********中文*************");
printTerm(a4,msg);
System.out.println("***********IK*************");
printTerm(a5,msg);
}
}
2KI分词器
- 扩展:根据语言环境配置新的词语
- 停用:根据业务需求将一些无效的,非法的,侮辱性
IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典
<entry key="ext_dict">ext.dic;</entry>
-->
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_dict">ext.dic;</entry>
<entry key="ext_stopwords">stopword.dic;</entry>
</properties>
ext.dic
拓展字典
王者荣耀
英雄联盟
stopword.dic
停止字典
还是
喜欢
索引文件创建
public class CreateIndex {
/*
* 1 确定索引的输出位置
* 2 准备一个具备使用Ik分词器输出流对象writer
* 3 内存的ducument(读取原数据封装)数据封装
* 4 输出写入到索引文件
* 域的类型(文本,数字,存储的索引数据)
*/
@Test
public void createIndex() throws IOException{
//确定一个输出的文件夹位置作为索引存在
Path path=Paths.get("d://index02");
//交给lucene的目录工具类
FSDirectory dir=FSDirectory.open(path);
//创建输出对象对应的配置对象
IndexWriterConfig config=new IndexWriterConfig(new IKAnalyzer6x());
//定义写出时的方式 append create create_or_append
config.setOpenMode(OpenMode.CREATE);
//配合dir路径创建writer
IndexWriter writer= new IndexWriter(dir,config);
//封装document
Document doc1=new Document();
Document doc2=new Document();
//title content image publisher
//name:域的名称
//title:读取源数据的值
//store:是否进行存储(document的数据输出到索引文件,并不是所有源数据都需要存储在索引中)
doc1.add(new TextField("title","国庆大阅兵",Store.YES));
doc1.add(new TextField("content","10月1日在北京举行的国庆周年",Store.YES));
doc1.add(new TextField("image","http://image.jt.com",Store.YES));
doc1.add(new TextField("publisher","新计划网",Store.YES));
doc1.add(new DoublePoint("price",500));
doc1.add(new StringField("price","500元",Store.YES));
doc2.add(new TextField("title","国庆假期",Store.YES));
doc2.add(new TextField("content","10月1日北京开始拥堵",Store.YES));
doc2.add(new StringField("image","http://image.jt.com/adf",Store.YES));
doc2.add(new TextField("publisher","娱乐网",Store.YES));
doc2.add(new DoublePoint("price",6000));
writer.addDocument(doc1);
writer.addDocument(doc2);
//直接写出
writer.commit();
}
}
索引文件搜索
public class SercherIndex {
/*
* lucune中将查询的功能封装到一个规范(接口类)Query
* 可以根据不同的查询功能使用不同的实现类
*/
/*
* 1 指定查询的索引
* 2 搜索对象的创建,使用一个输入流reader
* 3 封装查询条件query,不同功能,不同的实现类
* 4 浅查询实现数据的遍历使用
*/
//词项查询
@Test
public void termQuery() throws IOException{
//指定d://index01
Path path=Paths.get("d://index02");
FSDirectory dir=FSDirectory.open(path);
//获取输入流
IndexReader reader=DirectoryReader.open(dir);
//利用输入流,获取搜索对象
IndexSearcher search=new IndexSearcher(reader);
//根据不同的查询条件,封装query实现类对象
//TermQuery
//先提供一个词项 fld域名,关键字条件
Term term=new Term("content","北京");
Query query=new TermQuery(term);
//利用浅查询 获取结果集
TopDocs topDocs=search.search(query,10);//查前10条
System.out.println("总查询条数:"+topDocs.totalHits);
System.out.println("其中最大评分:"+topDocs.getMaxScore());
//返回的是一个封装搜索计算浅查询,和对应Document数据
//评分的对象,通过topDocs获取封装了documentid的评分
ScoreDoc[] docs=topDocs.scoreDocs;//查询的条数
for(ScoreDoc scoreDoc:docs){
//获取document的id值
System.out.println("document的id值为"+scoreDoc.doc);
//读取document
Document doc=search.doc(scoreDoc.doc);
//读取域属性
System.out.println("title:"+doc.get("title"));
System.out.println("content:"+doc.get("content"));
System.out.println("price:"+doc.get("price"));
System.out.println("publisher:"+doc.get("publisher"));
System.out.println("image:"+doc.get("image"));
}
}
//布尔查询条件
@Test
public void booleanQuery() throws Exception {
//指定d://index01
Path path=Paths.get("d://index02");
FSDirectory dir=FSDirectory.open(path);
//获取输入流
IndexReader reader=DirectoryReader.open(dir);
//利用输入流,获取搜索对象
IndexSearcher search=new IndexSearcher(reader);
//布尔查询
Query query1=new TermQuery(new Term("title","国庆"));
Query query2=new TermQuery(new Term("content","周年"));
//封装布尔子条件
BooleanClause bc1=new BooleanClause(query1,Occur.MUST);
BooleanClause bc2=new BooleanClause(query2,Occur.MUST);
//利用子条件bc1 bc2来创建布尔条件
Query query=new BooleanQuery.Builder().add(bc1).add(bc2).build();
//利用浅查询 获取结果集
TopDocs topDocs=search.search(query,10);//查前10条
System.out.println("总查询条数:"+topDocs.totalHits);
System.out.println("其中最大评分:"+topDocs.getMaxScore());
//返回的是一个封装搜索计算浅查询,和对应Document数据
//评分的对象,通过topDocs获取封装了documentid的评分
ScoreDoc[] docs=topDocs.scoreDocs;//查询的条数
for(ScoreDoc scoreDoc:docs){
//获取document的id值
System.out.println("document的id值为"+scoreDoc.doc);
//读取document
Document doc=search.doc(scoreDoc.doc);
//读取域属性
System.out.println("title:"+doc.get("title"));
System.out.println("content:"+doc.get("content"));
System.out.println("price:"+doc.get("price"));
System.out.println("publisher:"+doc.get("publisher"));
System.out.println("image:"+doc.get("image"));
}
}
//范围查询
@Test
public void rangeQuery()throws Exception{
//指定d://index01
Path path=Paths.get("d://index02");
FSDirectory dir=FSDirectory.open(path);
//获取输入流
IndexReader reader=DirectoryReader.open(dir);
//利用输入流,获取搜索对象
IndexSearcher search=new IndexSearcher(reader);
//范围查询,price doublepoint
Query query =DoublePoint.newRangeQuery("price", 500, 1000);
//利用浅查询 获取结果集
TopDocs topDocs=search.search(query,10);//查前10条
System.out.println("总查询条数:"+topDocs.totalHits);
System.out.println("其中最大评分:"+topDocs.getMaxScore());
//返回的是一个封装搜索计算浅查询,和对应Document数据
//评分的对象,通过topDocs获取封装了documentid的评分
ScoreDoc[] docs=topDocs.scoreDocs;//查询的条数
for(ScoreDoc scoreDoc:docs){
//获取document的id值
System.out.println("document的id值为"+scoreDoc.doc);
//读取document
Document doc=search.doc(scoreDoc.doc);
//读取域属性
System.out.println("title:"+doc.get("title"));
System.out.println("content:"+doc.get("content"));
System.out.println("price:"+doc.get("price"));
System.out.println("publisher:"+doc.get("publisher"));
System.out.println("image:"+doc.get("image"));
}
}
//多域查询
@Test
public void multiFieldQuery()throws Exception{
//指定d://index01
Path path=Paths.get("d://index02");
FSDirectory dir=FSDirectory.open(path);
//获取输入流
IndexReader reader=DirectoryReader.open(dir);
//利用输入流,获取搜索对象
IndexSearcher search=new IndexSearcher(reader);
//多域查询
//指定多个域
String[] fields={"title","content"};
//封装查询字符串条件,利用多域查询的解析器,对条件先进性分词计算
MultiFieldQueryParser parser=new MultiFieldQueryParser(fields, new IKAnalyzer6x());
Query query=parser.parse("北京国庆大阅 兵");
//底层封装多个TermQuery title 北京 title 国 庆 title 大 title 阅兵
//利用浅查询 获取结果集
TopDocs topDocs=search.search(query,10);//查前10条
System.out.println("总查询条数:"+topDocs.totalHits);
System.out.println("其中最大评分:"+topDocs.getMaxScore());
//返回的是一个封装搜索计算浅查询,和对应Document数据
//评分的对象,通过topDocs获取封装了documentid的评分
ScoreDoc[] docs=topDocs.scoreDocs;//查询的条数
for(ScoreDoc scoreDoc:docs){
//获取document的id值
System.out.println("document的id值为"+scoreDoc.doc);
//读取document
Document doc=search.doc(scoreDoc.doc);
//读取域属性
System.out.println("title:"+doc.get("title"));
System.out.println("content:"+doc.get("content"));
System.out.println("price:"+doc.get("price"));
System.out.println("publisher:"+doc.get("publisher"));
System.out.println("image:"+doc.get("image"));
}
}
}
- store.YES
当业务需求不需要在查询数据时使用返回 的数据,可以使用Store.NO将数据不保存 - TextField/StringField TextField的数据需要进行分词器的分词计算得到词项结果 StringField不需要进行分词计算(字符串的主键,web的连接地址,都不需要进行分词计算)
- DoublePoint 可以利用数字特性进行范围搜索
- MUST 必须包含
- MUST_NOT 必须不包含
- FILTER:=MUST 这个条件对应的所有结果不参加评分
- SHOULD: 和MUST FILTER同时存在时,不起作用