全文检索(Lucene)
1.数据的分类
1>结构化数据
格式固定,长度固定,数据类型固定
如:数据库中的数据
2>非结构化数据
word文档,pdf文档,邮件,html,txt
格式不固定,长度不固定,数据类型不固定
2.数据的查询
1>结构化数据的查询
SQL语句:结构化查询语言--查询结构化数据的方法(简单速度快)
2>非结构化数据的查询
从文本文件中找出包含Spring单词的文件
1.目测
2.使用程序把文件读取到内存中然后匹配字符串(顺序扫描)
3.把非结构化数据变成结构化数据
先根据空格进行字符串拆分,得到一个单词列表是,基于单词列表创建的一个索引然后查询索引根据单词和文档的对应关系来找到文档列表这个过程就叫做全文检索
索引:一个为了提高查询速度,创建的某种数据结构的集合
3.全文检索
1.>全文检索的概念
先创建索引然后查询索引的过程叫做全文检索
索引一次创建可以多次使用,表现为每次查询速度都很快
2.>全文检索的应用场景
1).做搜索引擎[百度,360,搜狗]
2).站内搜索[论坛搜索,微博,文章搜索]
3).电商搜索[淘宝,京东]
4).只要有搜索的地方都可以使用全文检索技术
4.什么是Lucene
概念:Lucene是一个基于Java开发全文检索工具包
1>Lucene实现全文检索的流程
1).创建索引
获得文档:--原始文档:要基于哪些数据来进行检索,那么这些数据就是原始文档
搜索引擎:使用爬虫获得原始文档
站内搜索:数据库中的数据
案例:直接使用IO流读取磁盘中的文件
2).构建文档对象
获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Doucment),文档中包括了一个一个的域(Field),域中存储内容
我们可以把一个磁盘上的一个文件当成一个document,Document中包括一些Field(file_name文件名称/file_path文件路径,file_size文件大小,file_context文件内容)
注意:每个Doucument可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)
对应每个原始文档创建一个Document对象
每个Doucemnt对象中包含多个域(field)
域中保存的就是原始文档的数据
域的名称
域的值
每个文档都有一个文档的编号为文本id,
3).分析文档
将原始内容创建为包含域(Field)的文档(Document)需要再对域中的内容进行分析分析的过程是经过对原始文档提取单词,将字母转为小写,去除标点符号,去吃停用词等过程生成最终的单元词汇,可以将单元理解为一个一个的单词
如: Lucene is a java full-text search engine
分析后 lucene is a java full text....
每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term,term中包含两部分一部分是文档的域名,另一部分是单词的内容
1.根据空格进行拆分,得到单词列表
2.把单词统一转化成,小写或者大写
3.去除标点符号
4.去除停用词(就是没有意义的词)
每个关键词都封装成一个Term对象中
Term找那个包含两部分内容
关键词所在的域
关键词本身
在不同的域中拆分出来的相同关键词是不同的Term
4).创建索引
对所有文档分析得出的词汇单元进行索,索引的目的是为了搜索,最终要实现只搜索有索引的词汇单元从而找到Document(文档)
注意:创建所以是对词汇单元索引,通过词语查找文档,这种索引的结构叫做倒排索引结构
传统的方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法就是顺序扫描方法,数据量大搜索慢
倒叙索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档较大
基于关键词链表来创建一个索引,保存到磁盘上(索引库中)
索引库中包含:
索引
Doucument
关键词和文档的对应关系
通过词语找文档,这种索引的结构叫做倒排索引结构
2>查询索引
查询索引也是搜索的过程,搜索就是用户输入关键字,从索引(index)中进行搜索的过程.根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容(这里指的是磁盘上的文件)
1).用户查询接口
全文检索系统提供给用户搜索界面供用户提交搜索的关键字,搜索完成展示搜索结果
Lucene不提供用户搜索界面的功能,需要用户根据自己的需求开发搜索界面
用户输入查询条件的地方
如:百度的搜索框
2).创建查询
用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要搜索的Field文档域,查询关键字等,查询对象会生成具体的查询方法
如:"fileName:lucene"表示要搜索Field域的内容为"lucene"的文档
要查询的域
要搜索的关键词
3).执行查询
搜索索引过程:
根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所连接的稳点链表
如:"fileName:lucene"表示搜索出fileName域中包含Lucene的文档
搜索过程就是在索引上查找域为fileName,并且关键字为Lucene的tem,并根据term找到文档id列表
根据要查询的关键词到对应的域进行搜索
把关键词找到根据关键词来找到对应的文档
4).渲染结果
以一个友好的界面将查询结果展示给用户,用户根据搜索结果找到自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中奖关键字高亮显示百度提供的快照等等
根据文档的ID找到文档对象需要对关键词进行高亮显示
需要进行分页处理
最终展示给用户看
5.入门程序
1.创建索引
环境:需要下载Lucene/最低要求JDK1.8
添加jar:lucene-analyzers-common-7.4.0.jar
lucene-core-7.4.0.jar
commons-io.jar
步骤:
public static void main(String[]args)throws Exception{
Directory directory = FSDirectory.open(new File("C:\\Users\\One\\Documents\\Tencent Files\\2633655104\\FileRecv\\type").toPath());
IndexWriter indexWriter=new IndexWriter(directory,new IndexWriterConfig());
File dir=new File("F:\\新建文件夹\\黑马57期\\讲义+笔记+资料\\流行框架\\61.会员版(2.0)-就业课(2.0)-Lucene\\lucene\\02.参考资料\\searchsource");
File[] files=dir.listFiles();
for (File f:files){
String fileName= f.getName();
String filePath=f.getPath();
String fileContext=FileUtils.readFileToString(f,"utf-8");
long fileSize=FileUtils.sizeOf(f);
Field fieldName=new TextField("name",fileName,Field.Store.YES);
Field fieldPath=new TextField("path",filePath,Field.Store.YES);
Field fieldContext=new TextField("context",fileContext,Field.Store.YES);
Field fieldSize=new TextField("size",fileSize+"",Field.Store.YES);
Document document=new Document();
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContext);
document.add(fieldSize);
indexWriter.addDocument(document);
}
indexWriter.close();
}
2.使用Luke查看索引库中的内容
3.查询索引库
步骤:
public static void main(String[] args) throws IOException {
Directory directory = FSDirectory.open(new File("C:\\Users\\One\\Documents\\Tencent Files\\2633655104\\FileRecv\\type").toPath());
IndexReader indexReader= DirectoryReader.open(directory);
IndexSearcher indexSearcher=new IndexSearcher(indexReader);
Query query=new TermQuery(new Term("context","spring"));
TopDocs topDocs=indexSearcher.search(query,11);
System.out.println("查询总记录数:"+topDocs.totalHits);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc s:scoreDocs
) {
int id=s.doc;
Document document=indexSearcher.doc(id);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println("-----------分割线--------");
}
indexReader.close();
}
}
6.分析器
默认使用的数据标准分析器StandardAnalyzer
1>如何查看分析器的分析效果
使用Analyzer对象的tokenStream方法可以返回一个TokenStream对象,此对象中包含了最终的分词结果
实现步骤:
1.创建一个Analyzer对象,Standardlyzer对象
2.使用分析器对象的tokenStream方法获得一个TokenStream对象
3.向TokenStream对象中设置一个引用,相当于是一个指针
4.调用tokenStream对象的rest方法如果不调用抛异常
5.使用while循环遍历tokenStream对象
6.关闭tokenStream对象
2>IKAnalyze的使用方法
1.把IKAnalyze添加到工程中
2.把配置文件和扩展词典都添加到工程的classpath下
注意:扩展词典是严禁使用windows记事本编辑的,保证扩展词典的编码是utf-8
WINDOWS环境下的记事本编码默认是UTF-8+BOM
扩展词典:添加一些新词
停用词词典:无意义的词或者是敏感词汇
7.索引库的维护
1.索引库的添加
1>.Field域的属性
是否分析:是否对域的内容进行分词处理,前提使我们要对域的内容进行查询
是否索引:将Field分析后的词整个Field值进行索引,只有索引方可以搜索到
如:商品名称,商品简介分析后进行索引,订单号,身份证号不用分析但也要索引,这些 将来都要作为查询的条件
是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取
如:商品名称,订单号,凡是将来要从Doucument中获取的Field都要存储
是否存储的标准:是否要将内容展示给用户
2.Field类:
1>.StringField(FieldName,FieldValue,Store.class)
数据类型:字符串
Analyzed是否分析:no
Indexed是否索引:yes
Stored是否存储:Yes or No
说明:这个Field用来构建一个字符串Field但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等)是否存储在文档中用Store.YES或者Store.NO决定
2>.LongPoint(String name,long...point)
数据类型:Long类型
Analyzed是否分析:yes
Indexed是否索引:yes
Stored是否存储: No
说明:可以使用LongPoint,IntPoint等类型存储数值类型的数据.让数值类型可以进行索引但是不能存储数据,如果想要存储数据还需要使用StoredField
3>.StoredField(FieldName,FieldValue)
数据类型:重载方法支持多种类型
Analyzed是否分析:no
Indexed是否索引:no
Stored是否存储:Yes
说明:这个Field用来构建不同类型Field不分析,不索引,但要Field存储在文档中
4>.TextField(FieldName,FieldValue,Store.No)OR TextField(FieldName,reader)
数据类型:字符串OR流
Analyzed是否分析:yes
Indexed是否索引:yes
Stored是否存储:Yes or No
说明:如果是一个Reader,lucene猜测内容比较多,会采用Unstored的策略
3.添加文档代码实现
public void addDocument() throws Exception {
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, config);
Document document = new Document();
document.add(new TextField("filename", "新添加的文档", Field.Store.YES));
document.add(new TextField("content", "新添加的文档的内容", Field.Store.NO));
document.add(new LongPoint("size", 1000l));
document.add(new StoredField("size", 1000l));
document.add(new StoredField("path", "d:/temp/1.txt"));
indexWriter.addDocument(document);
indexWriter.close();
}
4.删除全部索引
public void deleteAllIndex() throws Exception {
IndexWriter indexWriter = getIndexWriter();
indexWriter.deleteAll();
indexWriter.close();
}
说明:将索引目录的索引信息全部删除,直接彻底删除,无法恢复。
4.1.指定查询条件删除
public void deleteIndexByQuery() throws Exception {
IndexWriter indexWriter = getIndexWriter();
Query query = new TermQuery(new Term("filename", "apache"));
indexWriter.deleteDocuments(query);
indexWriter.close();
}
5.索引库的修改
public void updateIndex() throws Exception {
IndexWriter indexWriter = getIndexWriter();
Document document = new Document();
document.add(new TextField("filename", "要更新的文档", Field.Store.YES));
document.add(new TextField("content", " Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包," +
"它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。",
Field.Store.YES));
indexWriter.updateDocument(new Term("content", "java"), document);
indexWriter.close();
}
6.Lucene索引库查询
对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法,类似关系数据库SqL语法一样Lucene也有自己的查询语法
如:"name:lucene"表示查询Field的name为"lucene"的文档信息
可通过两种方法创建查询对象
1).使用Lucene提供的Query子类
2).使用QueryParser解析查询表达式
6.1.TermQuery
TermQuery通过项查询,TermQuery不适用分析器所以建议匹配不分词的Field域查询比如订单号,分类ID等(制定要查询的域和要查询的关键词)
public void testTermQuery() throws Exception {
Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
Query query = new TermQuery(new Term("content", "lucene"));
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("查询结果总数量:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("filename"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
}
indexSearcher.getIndexReader().close();
}
6.2.数值范围查询
public void testRangeQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
Query query = LongPoint.newRangeQuery("size", 0l, 10000l);
printResult(query, indexSearcher);
}
6.3.使用QueryParser查询
通过QueryParser也可以创建Query,QueryParser提供了一个Parser方法,此方法可以直接根据语法来查询,Query对象执行的查询语法可通过System.out.println(query)
查询需要使用到分析器,建议创建索引时使用的分析器和查询索引时使用的分析器要一致
public void testQueryParser() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("Lucene是java开发的");
printResult(query, indexSearcher);
}
private void printResult(Query query, IndexSearcher indexSearcher) throws Exception {
TopDocs topDocs = indexSearcher.search(query, 10);
System.out.println("查询结果总数量:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("filename"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
}
indexSearcher.getIndexReader().close();
}