但是如果老师不告诉你你坑爹的“坑”字在哪一页呢?也没有教你如何查字典呢?你只能从第一页的第一个字逐个的扫描下去,那样你真的是被坑了。查找的过程会相当的慢,甚至会让你崩溃,所以这种坑爹的事情我们不能去做。我们要重新思考此时的查询办法。
思考一下新华字典是怎么解决汉字的快速查找的?从一堆没有结构的内容中提取出来文字的位置信息(页码)、文字写法(文字本身)、汉语拼音,然后将它们重新整理、排序、归纳,最终形成一张结构化的表,我们叫做汉语拼音音节索引表。汉语拼音索引中记录了“坑”字在哪一页的信息,只要你知道“坑”字的汉语拼音,就可以快速的查找到“坑”字在哪,这样答案就出来了。下图是汉语拼音音节索引表:
从新华字典的例子总结一下,如何从一堆没有规律没有结构的信息中快速的查找我们需要的信息?最有效的方法就是先将信息重新组织(提取、整理、排序、归纳),形成新的集合(即一个更方便更高效查找的集合),然后查询这个结构化的集合,从中找出你要找的信息在原文中的位置。简单归纳成:非结构化结构化保存结构化查找结构化得到在非结构化中的定位
·基本的数据查询方案在面对查询量大的应用时会对数据库造成极大的压力,而且查询效率会很低。·改进后的数据查询方案将读写进行了分离,将查询量大的应用的查询请求分发给了索引库,查询直接走索引库,不走数据库,这样就有效的降低了数据库的压力,同时索引库查询的高效特性也能够保证查询效率。2.6.全文检索的应用场景全文检索应用最多的就是开发站内搜索服务。尤其是对于电商系统,大数据量的搜索都是使用的站内搜索服务。还有专业的搜索引擎中也有全文检索技术的使用,比如百度、Google等,但专业的搜索引擎不只使用这一种搜索技术。3.Lucene实现全文检索的流程3.1.创建索引和查询索引流程
说明:1.绿色表示创建索引过程,包括:采集数据构建文档对象分析文档对象创建索引(保存到索引库)
2.红色表示查询索引过程,包括:入口提交查询请求(查询关键字)创建查询对象执行查询(从索引库搜索)渲染结果显示查询结果3.2.索引流程用户将想要搜索的原始数据创建索引,索引内容存储在索引库(index)中。创建索引时不会改变原始文档的任何内容,只是将有用信息的拷贝重新组织成索引。假设有如下两个原始文档:【students.txt】:Studentsshouldbeallowedtogooutwiththeirfriends,butnotallowedtodrinkbeer.【myfriends.txt】:MyfriendJerrywenttoschooltoseehisstudentsbutfoundthemdrunkwhichisnotallowed.
3.2.1.采集数据
(手动编程)从互联网上、数据库、文件系统中等获取需要搜索的原始信息,这个过程就是信息采集,信息采集的目的是为了对原始内容进行索引。如何采集数据?1、互联网上的网页:可以使用工具将网页抓取到本地生成html文件。2、数据库中的数据:可以直接连接数据库用SQL查询数据。3、文件系统中的文件:可以通过I/O操作读取文件的内容。
上图是将磁盘上的一个文件采集出来的数据放入一个Document对象。Document对象中包括四个Field(file_name、file_path、file_size、file_content)3.2.3.分析文档对象(重点)(Lucene自动完成)分析文档主要是对文档的Field域进行分析,目的是为了创建索引做好准备。分析的过程是将域(Field)的内容转换成最基本的索引单元——项(Term)的过程。
3.2.3.1.分词组件(Tokenizer)分词组件(Tokenizer)会做以下几件事情(这个过程称为:Tokenize):1.分词器将Field域内容分成一个一个单独的单词2.标点符号过滤器去除内容中的标点符号3.停用词过滤器去除停用词(stopword)什么是停用词?所谓停词(Stopword)就是一种语言中没有具体含义的词,因而大多数情况下不会作为搜索的关键词,这样一来创建索引时能减少索引的大小。英语中停词(Stopword)如:”the”、”a”、”this”,中文有:”的,得”等。不同语种的分词组件(Tokenizer),都有自己的停词(stopword)集合。
综上所述,分析文档的最终产物是Term,Term是创建索引的最小单元,也是搜索索引时的最小单元。3.2.4.创建索引(Lucene自动完成)
3.2.4.1.创建字典表利用得到的词项(Term)创建一个字典表,一列是Term词项,一列是文档ID(DocId)字典表如下:TermDocIdstudent1allow1go1their1friend1allow1drink1beer1my2friend2jerry2go2school2see2his2student2find2them2drink2allow2
3.2.4.2.对字典表按字母顺序排序对字典表按字母顺序排序:排序结果如下:TermDocIdallow1allow1allow2beer1drink1drink2find2friend1friend2go1go2his2jerry2my2school2see2student1student2their1them2
3.2.4.3.合并相同词项,归纳文档倒排链表创建好的Term词项实际是包含两部分信息:一是Term出自哪个域,二是Term的内容。合并相同的词项(Term)成为文档倒排(PostingList)链表。●合并规则:●在比较Term是否相同时,不考虑是否在同一个Document对象中,合并时暂时忽略它。●不同的域(Field)中拆分出来的相同的单词是不同的Term,不能合并。例如:文件名中包含apache和文件内容中包含的apache是不同的Term。●同名域(Field)的相同单词是相同的Term,可以合并。例如:两个文档中都有【文件名】Field域中都含有Java,这两个Java就是一个Term(域和单词都相同)
例子是以两个文档的【content】域作为演示的例子,因此只要单词相同就是相同的Term,就可以合并。合并结果如下:合并的同时要记录这个Term来自于哪个文档以及出现的次数。
●DocumentFrequency(DF):文档频次,表示多少文档出现过此词(Term)●Frequency(TF):词频,表示某个文档中该词(Term)出现过几次例如:对词项(Term)“allow”来讲,总共有两篇(DF)文档包含此Term,Term后面的文档链表总共有两个,第一个表示包含“allow”的第一篇文档,即DocId=1的文档,此文档中“allow”出现了2次(TF),第二个表示包含“allow”的第二个文档,即DocId=2的文档,此文档中,”allow”出现了1次(TF)。索引表+文档倒排链表+文档对象集合,共同组成了索引库●索引表是保存索引词项的●文档倒排链表是保存包含词项的文档ID的●文档对象集合是保存文档具体内容的3.2.5.索引流程总结
3.3.查询索引查询索引就是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容。3.3.1.用户用户可以是自然人,也可以是远程调用的程序。3.3.2.用户搜索界面(手动编程)搜索界面用于提交用户搜索关键字的,也相当于采集数据的作用。比如:
对条件进行解析并执行查询:(三步)●第一步:对查询语句进行词法分析、语法分析及语言处理1.词法分析如上述例子中,经过词法分析,得到单词有台灯,LED,调光开关,关键字有AND,NOT。注意:关键字必须大写,否则就作为普通单词处理。关键字有AND、OR、NOT。
2.语法分析如果发现查询语句不满足语法规则,则会报错。如product_keywords:台灯NOTAND调光开关,则会出错。如果查询语句满足语法规则,就会形成语法树如下:3.语言处理如LED变成led等。经过第三步,我们得到一棵经过语言处理的语法树。
●第二步:搜索索引,得到符合语法树的文档1.首先,在反向索引表中,分别找出包含lucene,learn,hadoop的文档链表。
2.其次,对包含lucene,learn的链表进行合并操作,得到既包含lucene又包含learn的文档链表。
3.然后,将此链表与hadoop的文档链表进行差操作,去除包含hadoop的文档,从而得到既包含lucene又包含learn而且不包含hadoop的文档链表。
4.此文档链表就是我们要找的文档。
3.3.6.渲染结果以一个友好的界面将查询结果展示给用户,用户根据搜索结果找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。
Lucene基本开发jar包:lucene-core-4.10.3.jarlucene-analyzers-common-4.10.3.jarlucene-queryparser-4.10.3.jar
1)lucene-core-4.10.3.jar的位置:这是Lucene的核心jar包
2)lucene-analyzers-common-4.10.3.jar的位置:这是Lucene的分析器的核心jar包
3)lucene-queryparser-4.10.3.jar的位置:这是Lucene的查询解析器jar包其它:用于处理文件内容的工具包commons-io-2.4.jar
4.3.创建java工程创建一个java工程,编码格式utf-8,并导入jar包并导入Junit测试的jar。5.入门程序5.1.需求实现一个文件的搜索功能,通过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都需要找出来。还可以根据中文词语进行查询,并且需要支持多个条件查询。本案例中的原始内容就是磁盘上的文件,如下图:
这里我们要搜索的文档是磁盘上的文本文件,我们要把凡是文件名或文件内容中包括关键字的文件都要找出来,所以这里要对文件名和文件内容创建索引。
本案例我们要获取磁盘上文件的内容,可以通过文件流来读取文本文件的内容,对于pdf、doc、xls等文件可通过第三方提供的解析工具读取文件内容,比如ApachePOI读取doc和xls的文件内容。
publicclassCreateIndexTest{/***创建IndexWriter(创建索引准备工作)*/privateIndexWritercreateIndexWriter(StringindexRepositoryPath)throwsException{//创建Directory对象Directorydir=FSDirectory.open(newFile(indexRepositoryPath));//索引库还可以存放到内存中//Directorydirectory=newRAMDirectory();//创建一个标准分析器Analyzeranalyzer=newStandardAnalyzer();//创建IndexWriterConfig对象//参数1:Lucene的版本信息,可以选择对应的Lucene版本也可以使用LATEST//参数2:分析器对象IndexWriterConfigconfig=newIndexWriterConfig(Version.LATEST,analyzer);//创建IndexWriter对象returnnewIndexWriter(dir,config);}
5.2.3.使用Luke工具查看索引文件使用luke工具。Luke是一个便于使用Lucene开发和诊断的第三方工具,它可以访问现有利用Lucene创建的索引,并允许显示和修改。1.启动工具:直接双击【start.bat】或者在控制台输入【java-jarlukeall-4.10.3.jar】
2.选择索引库位置
3.索引域的展示效果:
4.文档域的展示效果:
5.3.3.TopDocsLucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:
6.3.中文分析器对于分词来说,不同的语言,分词规则是不同的,比如英语每个单词都是用空格分隔,所以拆分词的规则比较简单,我们可以简单以空格判断某个字符串是否为一个单词,比如IloveChina,love和China很容易被程序区分开来。汉字就不同了,中文是以字为单位的,字组成词,字和词再组成句子。所以它的词必须根据语义分析后才能正确的拆分,所以拆分词的规则会很复杂。比如:“我爱中国”,电脑不知道“中国”是一个词语还是“爱中”是一个词语。把中文的句子切分成有意义的词就是中文分词,也称切词。“我爱中国”,正确的分词结果是:我、爱、中国。6.3.1.Lucene自带中文分析器StandardAnalyzer:单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,效果:“我”、“爱”、“中”、“国”。CJKAnalyzer二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。
使用方法:第一步:把jar包添加到工程中第二步:把配置文件和扩展词典和停用词词典添加到classpath下
注意:mydict.dic和ext_stopword.dic文件的格式为UTF-8,注意是无BOM的UTF-8编码。
使用EditPlus.exe保存为无BOM的UTF-8编码格式,如下图:
6.4.1.添加jar包在【资料\jar\IK】下找到IKAnalyzer的jar包
6.4.2.修改代码IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将创建索引的测试代码中的【StandardAnalyzer】改为【IKAnalyzer】测试中文分词效果。可以和之前使用StandardAnalyzer分析器创建的索引可以对比一下:StandardAnalyzer分析得出的索引结果:
IKAnalyzer分析得出的索引结果:
从结果看出IKAnalyzer能更好的从语义上识别中文,并做出比较正确的切分词。6.4.3.扩展词库的使用IKAnalyzer允许用户扩展自己的中文词库,包括扩展词库和停用词库。扩展词库:是把一些特殊的专有名词加进来,这样分词的时候就会把专有名词当成一个整体,不会被切分。停用词库:是把一些想过滤掉的词加进来,这样分词后就会被过滤器过滤掉,不作为索引的语汇单元。6.4.3.1.扩展词库文件与停用词库文件下载下来的IK压缩包中可能有停用词库,但没有扩展词库,但可以手动创建,但要注意:在创建词库时,不要用windows自带的记事本保存词库文件,因为windows默认格式是含有bom文件头的,这是个不可见文件标识符号,IK识别的时候会出错,因为非windows系统都是不带bom文件头的。
扩展词库【ext.dic】编程思想传智播客
6.4.3.3.测试为了便于测试结果的确认,在数据库book表中把每一条记录的description中都加入:【《计算机科学丛书:Java编程思想(第4版)》【传智播客】】这一段话,这样可以增加【编程思想】和【传智播客】的出现频率,搜索排名会靠前。1.不加扩展词库和停用词库时创建索引的结果:停用词没有被过滤掉:and,的,the等都被加进了索引库扩展词【编程思想】【传值播客】被分开了
2.添加停用词库后重新创建索引(将原来的索引文件删除,注意:要先关闭Luke)
如果加入log4j,再次运行的log:
已经看不到被停用的单词了:
3.添加扩展词库后重新创建索引(将原来的索引文件删除,注意:要先关闭Luke)
再次运行的log:
已经看到扩展词没有被切分:【传值播客】是纯粹的专有名词,所以完全的被保留,没有切分【编程思想】并不是纯粹的专有名词,在IK的内部的中文分词器中仍然会识别【编程】和【思想】,然后你又追加了【编程思想】,所以最终是三个词【编程】【思想】【编程思想】
6.5.分析器Analyzer使用时机6.5.1.索引时使用的Analyzer创建索引时对文档对象的内容进行分析是一个必要的过程,大部分文档内容都是需要被分析的,但也有一些特殊的Field域的内容是不用分析,可以直接作为Term创建索引。对于一些Field可以不用分析:1、不作为查询条件的内容,比如文件路径2、不是匹配内容中的词而匹配Field的整体内容,比如订单号、身份证号等。
6.5.2.搜索时使用Analyzer用户输入的查询内容也需要进行分析,这个过程和创建索引时的分析是一样的,因此他们必须使用一致的分析器对象,否则会出现双方分析出来的Term对应不上,这样就无法进行查询了。注意:搜索使用的分析器要和索引使用的分析器一致。和索引时一样,查询是也存在一些特殊的查询是不需要分析的,比如根据订单号、身份证号查询等。