ES这个组件其实挺重要的,早就应该了解的组件,借着这次机会,我尽量把这个组件摸摸透
ES和结构型数据库的横向对比
MySQL
ElasticSearch
Database
Index
Table
Type
Row
Document
Column
Field
Schema
Mapping
Index
Everything Indexed by default
SQL
Query DSL(查询专用语言)
这个表格对ES介绍的很好,从概念上很容易就能把ES和MySQL联系到一起。
ElasticSearch 版本新特性 ElasticSearch Java API ES的Java REST Client有两种风格:
Java Low Level REST Client: 用于Elasticsearch的官方低级客户端。它允许通过http与Elasticsearch集群通信。将请求编排和响应反编排留给用户自己处理。它兼容所有的Elasticsearch版本。
Java High Level REST Client: 用于Elasticsearch的官方高级客户端。它是基于低级客户端的,它提供很多API,并负责请求的编排与响应的反编排。
在 Elasticsearch 7.0 中不建议使用TransportClient,并且在8.0中会完全删除TransportClient。因此,官方更建议我们用Java High Level REST Client,它执行HTTP请求,而不是序列号的Java请求。
ElasticSearch Query DSL 查询与过滤 数据检索分为两种情况:查询 和过滤
Query会对检索结果进行评分 ,注重的点是匹配程度,计算的是查询与文档的相关程度,计算完成之后会酸醋一个评分,记录在_score
中,最终按照_score
进行排序。
Filter过滤不会对检索结果进行评分,注重的点是是否匹配,,所以速度 要快一点,并且过滤的结果会被缓存到内存中,性能要比Query高很多。
简单查询 最简单的DSL查询表达式:
1 2 3 4 5 6 GET /_search //查找整个ES中所有索引的内容 { "query":{ "match_all": {} } }
/_search 查找整个ES中所有索引的内容
query 为查询关键字,类似的还有aggs
为聚合关键字
match_all 匹配所有的文档,也可以写match_none
不匹配任何文档
返回结果:
took: 表示我们执行整个搜索请求消耗了多少毫秒
timed_out: 表示本次查询是否超时
这里需要注意当timed_out
为True时也会返回结果,这个结果是在请求超时时ES已经获取到的数据,所以返回的这个数据可能不完整。
且当你收到timed_out
为True之后,虽然这个连接已经关闭,但在后台这个查询并没有结束,而是会继续执行
_shards: 显示查询中参与的分片信息,成功多少分片失败多少分片等
hits: 匹配到的文档的信息,其中total
表示匹配到的文档总数,max_score
为文档中所有_score
的最大值
hits中的hits
数组为查询到的文档结果,默认包含查询结果的前十个文档,每个文档都包含文档的_index
、_type
、_id
、_score
和_source
数据
结果文档默认情况下是按照相关度(_score)进行降序排列,也就是说最先返回的是相关度最高的文档,文档相关度意思是文档内容与查询条件的匹配程度,上边的查询与过滤中有介绍
指定索引
指定固定索引:
指定多个索引:
1 GET /index1,index2/_search
用*号匹配,在匹配到的所有索引下查找数据
分页查询 因为hits
默认只展示10个文档,那我们如何查询10个以后的文档呢?ES中给力size和from两个参数。
size: 设置一次返回的结果数量,也就是hits
中文档的数量,默认是10
from: 设置从第几个结果开始往后查询,默认值是0
1 2 3 4 5 6 7 8 GET /_search { "size": 5, "from": 10, "query":{ "match_all": {} } }
这条命令的意义是:显示第11条到15个文档的数据。
match_all
为查询所有记录,常用的查询关键字在ES中还有match
、multi_match
、query_string
、term
、range
1 2 3 4 5 6 7 8 GET /_search { "query":{ "match": { "host":"ops-coffee.cn" } } }
1 2 3 4 5 6 7 8 9 10 GET /_search { "query":{ "multi_match": { "query":"ops-coffee.cn", "fields":["host","http_referer"] } } } //在多个字段上搜索时用multi_match
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 GET /_search { "query":{ "query_string": { "query":"(a.ops-coffee.cn) OR (b.ops-coffee.cn)", "fields":["host"] } } } //可以在查询里边使用AND或者OR来完成复杂的查询 GET /_search { "query":{ "query_string": { "query":"host:a.ops-coffee.cn OR (host:b.ops-coffee.cn AND status:403)" } } } //以上表示查询(host为a.ops-coffee.cn)或者是(host为b.ops-coffee.cn且status为403)的所有记录\ 与其像类似的还有个simple_query_string的关键字,可以将query_string中的AND或OR用+或|这样的符号替换掉
1 2 3 4 5 6 7 8 9 10 11 //term表示了精确匹配,精确匹配的可以是数字,时间,布尔值或者是设置了not_analyzed不分词的字符串 GET /_search { "query":{ "term": { "status": { "value": 404 } } } }
1 2 3 4 5 6 7 8 9 //匹配多个值 GET /_search { "query": { "terms": { "status":[403,404] } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 //range用来查询落在指定区间里的数字或者时间 GET /_search { "query": { "range":{ "status":{ "gte": 400, "lte": 599 } } } } //range用来查询落在指定区间内的数字或者时间 范围关键字主要有四个: gt: 大于 gte: 大于等于 lt: 小于 lte: 小于等于 并且:当range把日期作为查询范围时,我们需注意下日期的格式,官方支持的日期格式主要有两种 1.时间戳(ms) get /_search { "query": { "range": { "@timestamp": { "gte": 1557676800000, "lte": 1557680400000, "format":"epoch_millis" } } } } 2.日期字符串 GET /_search { "query": { "range":{ "@timestamp":{ "gte": "2019-05-13 18:30:00", "lte": "2019-05-14", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd", "time_zone": "+08:00" } } } } 日期格式可以按照自己的习惯输入,只需要format字段指定匹配的格式,如果格式有多个就用||分开,不过推荐用相同的日期格式。 如果日期中缺少年月日这些内容,那么缺少的部分会用unix的开始时间(即1970年1月1日)填充,当你将"format":"dd"指定为格式时,那么"gte":10将被转换成1970-01-10T00:00:00.000Z elasticsearch中默认使用的是UTC时间,所以我们在使用时要通过time_zone来设置好时区,以免出错
组合查询 通常我们可能需要将很多个条件组合在一起查处最后的结果,这个时候就需要使用es提供的bool
来实现。
Ex. 要查询host
为ops-coffee.cn
且http_x_forworded_for
为111.18.78.128
且status
不为200的所有数据就可以使用下边的语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 GET /_search { "query":{ "bool": { "filter": [ {"match": { "host": "ops-coffee.cn" }}, {"match": { "http_x_forwarded_for": "111.18.78.128" }} ], "must_not": { "match": { "status": 200 } } } } } 组合查询设计四个关键字组合设计查询之间的关系。分别为: must: 类似于SQL中的AND,必须包含 must_not: 类似于SQL中的NOT,必须不包含 should: 满足这些条件中的任何条件都会增加评分_score,不满足也不影响,should只会影响查询结果的_score值,并不会影响结果的内容 filter: 与must相似,但不会对结果进行相关性评分_score,大多数情况下我们对于日志的需求都无相关性的要求,所以建议查询的过程中多用filter
全文检索 全文检索就是对一篇文章进行索引,可以根据关键字搜索,类似于mysql里的like语句。
全文索引就是把内容根据词的意义进行分词,然后分别创建索引,例如”你们的激情是因为什么事情来的” 可能会被分词成:“你们“,”激情“,“什么事情“,”来“ 等token,这样当你搜索“你们” 或者 “激情” 都会把这句搜出来。
ES调优 调优
Lucene 介绍 : Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包,由资深全文检索专家Doug Cutting所撰写,它是一个全文检索引擎的架构 ,提供了完整的创建索引和查询索引,以及部分文本分析的引擎。
特色 : Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎,Lucene在全文检索领域是一个经典的祖先,现在很多检索引擎都是在其基础上创建的,思想是相通的。
Lucene是根据关健字来搜索的文本搜索工具,只能在某个网站内部搜索文本内容,不能跨网站搜索
为什么有了数据库还要使用Lucene:
(1)SQL只能针对数据库表搜索,不能直接针对硬盘上的文本搜索
(2)SQL没有相关度排名
(3)SQL搜索结果没有关健字高亮显示
(4)SQL需要数据库的支持 ,数据库本身需要内存开销较大,例如:Oracle
(5)SQL搜索有时较慢 ,尤其是数据库不在本地时,超慢,例如:Oracle
以上所说的,我们如果使用SQL的话,是做不到的。因此我们就学习Lucene来帮我们在站内根据文本关键字来进行搜索数据 !
我们如果网站需要根据关键字来进行搜索,可以使用SQL,也可以使用Lucene…那么我们Lucene和SQL是一样的,都是在持久层中编写代码的 。
Lucene中存的就是一系列的二进制压缩文件和一些控制文件 ,它们位于计算机的硬盘上,这些内容统称为索引库 ,索引库有二部份组成:
(1)原始记录
(2)词汇表
按照一定的拆分策略(即分词器)将原始记录中的每个字符拆开后,存入一个供将来搜索的表
也就是说:Lucene存放数据的地方我们通常称之为索引库,索引库又分为两部分组成:原始记录和词汇表 ….
原始记录和词汇表 当我们想要把数据存到索引库的时候,我们首先存入的是将数据存到原始记录上面去….
又由于我们给用户使用的时候,用户使用的是关键字来进行查询我们的具体记录 。因此,我们需要把我们原始存进的数据进行拆分 !将拆分出来的数据存进词汇表中 。
词汇表就是类似于我们在学Oracle中的索引表,拆分的时候会给出对应的索引值。
一旦用户根据关键字来进行搜索,那么程序就先去查询词汇表中有没有该关键字,如果有该关键字就定位到原始记录表中,将符合条件的原始记录返回给用户查看 。
我们查看以下的图方便理解:
到了这里,有人可能就会疑问:难道原始记录拆分的数据都是一个一个汉字进行拆分的吗??然后在词汇表中不就有很多的关键字了???
其实,我们在存到原始记录表中的时候,可以指定我们使用哪种算法来将数据拆分,存到词汇表中…..我们的图是Lucene的标准分词算法,一个一个汉字进行拆分 。我们可以使用别的分词算法,两个两个拆分或者其他的算法。
Lucene程序: 首先要导入必要的Lucene的必要开发包
lucene-core-3.0.2.jar【Lucene核心】
lucene-analyzers-3.0.2.jar【分词器】
lucene-highlighter-3.0.2.jar【Lucene会将搜索出来的字,高亮显示,提示用户】
lucene-memory-3.0.2.jar【索引库优化策略】
创建User对象,User对象封装了数据….
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class User { private String id ; private String userName; private String sal; public User () { } public User (String id, String userName, String sal) { this .id = id; this .userName = userName; this .sal = sal; } public String getId () { return id; } public void setId (String id) { this .id = id; } public String getUserName () { return userName; } public void setUserName (String userName) { this .userName = userName; } public String getSal () { return sal; } public void setSal (String sal) { this .sal = sal; } }
我们想要使用Lucene来查询出站内的数据,首先我们得要有个索引库吧!于是我们先创建索引库,将我们的数据存到索引库中 。
创建索引库的步骤:
1)创建JavaBean对象
2)创建Docment对象
3)将JavaBean对象所有的属性值,均放到Document对象中去,属性名可以和JavaBean相同或不同
4)创建IndexWriter对象
5)将Document对象通过IndexWriter对象写入索引库中
6)关闭IndexWriter对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Test public void createIndexDB () throws Exception { User user = new User("1" , "钟福成" , "未来的程序员" ); Document document = new Document(); document.add(new Field("id" , user.getId(), Field.Store.YES, Field.Index.ANALYZED)); document.add(new Field("userName" , user.getUserName(), Field.Store.YES, Field.Index.ANALYZED)); document.add(new Field("sal" , user.getSal(), Field.Store.YES, Field.Index.ANALYZED)); Directory directory = FSDirectory.open(new File("E:/createIndexDB" )); Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30); IndexWriter.MaxFieldLength maxFieldLength = IndexWriter.MaxFieldLength.LIMITED; IndexWriter indexWriter = new IndexWriter(directory, analyzer, maxFieldLength); indexWriter.addDocument(document); indexWriter.close(); }
于是,我们现在用一个关键字,把索引库的数据读取。看看读取数据是否成功。
根据关键字查询索引库中的内容:
1)创建IndexSearcher对象
2)创建QueryParser对象
3)创建Query对象来封装关键字
4)用IndexSearcher对象去索引库中查询符合条件的前100条记录,不足100条记录的以实际为准
5)获取符合条件的编号
6)用indexSearcher对象去索引库中查询编号对应的Document对象
7)将Document对象中的所有属性取出,再封装回JavaBean对象中去,并加入到集合中保存,以备将之用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Test public void findIndexDB () throws Exception { Directory directory = FSDirectory.open(new File("E:/createIndexDB" )); IndexSearcher indexSearcher = new IndexSearcher(directory); Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30); QueryParser queryParser = new QueryParser(Version.LUCENE_30, "userName" , analyzer); String keyWords = "钟" ; Query query = queryParser.parse(keyWords); TopDocs topDocs = indexSearcher.search(query, 100 ); for (int i = 0 ; i < topDocs.scoreDocs.length; i++) { ScoreDoc scoreDoc = topDocs.scoreDocs[i]; int no = scoreDoc.doc; Document document = indexSearcher.doc(no); String id = document.get("id" ); String userName = document.get("userName" ); String sal = document.get("sal" ); User user = new User(id, userName, sal); System.out.println(user); }
代码说明: 我们的Lucene程序就是大概这么一个思路:将JavaBean对象封装到Document对象中,然后通过IndexWriter把document写入到索引库中。当用户需要查询的时候,就使用IndexSearcher从索引库中读取数据,找到对应的Document对象,从而解析里边的内容,再封装到JavaBean对象中让我们使用 。
代码优化 我们再次看回我们上一篇快速入门写过的代码,我来截取一些有代表性的:
以下代码在把数据填充到索引库,和从索引库查询数据的时候,都出现了。是重复代码 !
1 2 3 4 Directory directory = FSDirectory.open(new File("E:/createIndexDB" )); Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_30);
以下的代码其实就是将JavaBean的数据封装到Document对象中,我们是可以通过反射来对其进行封装 ….如果不封装的话,我们如果有很多JavaBean都要添加到Document对象中,就会出现很多类似的代码.
1 2 3 document.add(new Field("id" , user.getId(), Field.Store.YES, Field.Index.ANALYZED)); document.add(new Field("userName" , user.getUserName(), Field.Store.YES, Field.Index.ANALYZED)); document.add(new Field("sal" , user.getSal(), Field.Store.YES, Field.Index.ANALYZED));
以下代码就是从Document对象中把数据取出来,封装到JavaBean去。如果JavaBean中有很多属性,也是需要我们写很多次类似代码….
1 2 3 4 5 String id = document.get("id" ); String userName = document.get("userName" ); String sal = document.get("sal" ); User user = new User(id, userName, sal);
工具类 编写工具类的时候,值得注意的地方:
当我们得到了对象的属性的时候,就可以把属性的get方法封装起来
得到get方法,就可以调用它,得到对应的值
在操作对象的属性时,我们要使用暴力访问
如果有属性,值,对象这三个变量,我们记得使用BeanUtils组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 import org.apache.commons.beanutils.BeanUtils;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.standard.StandardAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.store.Directory;import org.apache.lucene.store.FSDirectory;import org.apache.lucene.util.Version;import org.junit.Test;import java.io.File;import java.lang.reflect.Field;import java.lang.reflect.Method;public class LuceneUtils { private static Directory directory; private static Analyzer analyzer; private static IndexWriter.MaxFieldLength maxFieldLength; private LuceneUtils () {} static { try { directory = FSDirectory.open(new File("E:/createIndexDB" )); analyzer = new StandardAnalyzer(Version.LUCENE_30); maxFieldLength = IndexWriter.MaxFieldLength.LIMITED; } catch (Exception e) { e.printStackTrace(); } } public static Directory getDirectory () { return directory; } public static Analyzer getAnalyzer () { return analyzer; } public static IndexWriter.MaxFieldLength getMaxFieldLength () { return maxFieldLength; } public static Document javaBean2Document (Object object) { try { Document document = new Document(); Class<?> aClass = object.getClass(); Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { String name = field.getName(); String method = "get" + name.substring(0 , 1 ).toUpperCase() + name.substring(1 ); Method aClassMethod = aClass.getDeclaredMethod(method, null ); String value = aClassMethod.invoke(object).toString(); System.out.println(value); document.add(new org.apache.lucene.document.Field(name, value, org.apache.lucene.document.Field.Store.YES, org.apache.lucene.document.Field.Index.ANALYZED)); } return document; } catch (Exception e) { e.printStackTrace(); } return null ; } public static Object Document2JavaBean (Document document, Class aClass) { try { Object obj = aClass.newInstance(); Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true ); String name = field.getName(); String value = document.get(name); BeanUtils.setProperty(obj, name, value); } return obj; } catch (Exception e) { e.printStackTrace(); } return null ; } @Test public void test () { User user = new User(); LuceneUtils.javaBean2Document(user); } }
使用LuceneUtils改造程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Test public void createIndexDB () throws Exception { User user = new User("2" , "钟福成2" , "未来的程序员2" ); Document document = LuceneUtils.javaBean2Document(user); IndexWriter indexWriter = new IndexWriter(LuceneUtils.getDirectory(), LuceneUtils.getAnalyzer(), LuceneUtils.getMaxFieldLength()); indexWriter.addDocument(document); indexWriter.close(); } @Test public void findIndexDB () throws Exception { IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.getDirectory()); QueryParser queryParser = new QueryParser(Version.LUCENE_30, "userName" , LuceneUtils.getAnalyzer()); String keyWords = "钟" ; Query query = queryParser.parse(keyWords); TopDocs topDocs = indexSearcher.search(query, 100 ); for (int i = 0 ; i < topDocs.scoreDocs.length; i++) { ScoreDoc scoreDoc = topDocs.scoreDocs[i]; int no = scoreDoc.doc; Document document = indexSearcher.doc(no); User user = (User) LuceneUtils.Document2JavaBean(document, User.class); System.out.println(user); } }
索引库优化: 我们已经可以创建索引库并且从索引库读取对象的数据了。其实索引库还有地方可以优化的…
合并文件 我们把数据添加到索引库中的时候,每添加一次,都会帮我们自动创建一个cfs文件 …
这样其实不好,因为如果数据量一大,我们的硬盘就有非常非常多的cfs文件了…..其实索引库会帮我们自动合并文件的,默认是10个 。
如果,我们想要修改默认的值,我们可以通过以下的代码修改:
1 2 3 4 5 indexWriter.optimize(); indexWriter.setMergeFactor(3 );
我们的目前的程序是直接与文件进行操作,这样对IO的开销其实是比较大的。而且速度相对较慢….我们可以使用内存索引库来提高我们的读写效率…
对于内存索引库而言,它的速度是很快的,因为我们直接操作内存…但是呢,我们要将内存索引库是要到硬盘索引库中保存起来的。当我们读取数据的时候,先要把硬盘索引库的数据同步到内存索引库中去的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Article article = new Article(1 ,"培训" ,"传智是一家Java培训机构" ); Document document = LuceneUtil.javabean2document(article); Directory fsDirectory = FSDirectory.open(new File("E:/indexDBDBDBDBDBDBDBDB" )); Directory ramDirectory = new RAMDirectory(fsDirectory); IndexWriter fsIndexWriter = new IndexWriter(fsDirectory,LuceneUtil.getAnalyzer(),true ,LuceneUtil.getMaxFieldLength()); IndexWriter ramIndexWriter = new IndexWriter(ramDirectory,LuceneUtil.getAnalyzer(),LuceneUtil.getMaxFieldLength()); ramIndexWriter.addDocument(document); ramIndexWriter.close(); fsIndexWriter.addIndexesNoOptimize(ramDirectory); fsIndexWriter.close();
分词器 我们在前面中就已经说过了,在把数据存到索引库的时候,我们会使用某些算法,将原始记录表的数据存到词汇表中…..那么这些算法总和我们可以称之为分词器
分词器: ** 采用一种算法,将中英文本中的字符拆分开来,形成词汇,以待用户输入关健字后搜索**
对于为什么要使用分词器,我们也明确地说过:由于用户不可能把我们的原始记录数据完完整整地记录下来,于是他们在搜索的时候,是通过关键字进行对原始记录表的查询….此时,我们就采用分词器来最大限度地匹配相关的数据
分词器
步一:按分词器拆分出词汇
步二:去除停用词和禁用词
步三:如果有英文,把英文字母转为小写,即搜索不分大小写
API:
我们在选择分词算法的时候,我们会发现有非常非常多地分词器API,我们可以用以下代码来看看该分词器是怎么将数据分割的 :
1 2 3 4 5 6 7 8 9 private static void testAnalyzer (Analyzer analyzer, String text) throws Exception { System.out.println("当前使用的分词器:" + analyzer.getClass()); TokenStream tokenStream = analyzer.tokenStream("content" ,new StringReader(text)); tokenStream.addAttribute(TermAttribute.class); while (tokenStream.incrementToken()) { TermAttribute termAttribute = tokenStream.getAttribute(TermAttribute.class); System.out.println(termAttribute.term()); } }
在实验完之后,我们就可以选择恰当的分词算法了….
IKAnalyzer分词器 这是一个第三方的分词器,我们如果要使用的话需要导入对应的jar包
IKAnalyzer3.2.0Stable.jar
步二:将IKAnalyzer.cfg.xml和stopword.dic和xxx.dic文件复制到MyEclipse的src目录下,再进行配置,在配置时,首行需要一个空行
这个第三方的分词器有什么好呢????他是中文首选的分词器 …也就是说:他是按照中文的词语来进行拆分的!
对搜索结果进行处理 搜索结果高亮 我们在使用SQL时,搜索出来的数据是没有高亮的…而我们使用Lucene,搜索出来的内容我们可以设置关键字为高亮 …这样一来就更加注重用户体验了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 String keywords = "钟福成" ; List<Article> articleList = new ArrayList<Article>(); QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content" ,LuceneUtil.getAnalyzer()); Query query = queryParser.parse(keywords); IndexSearcher indexSearcher = new IndexSearcher(LuceneUtil.getDirectory()); TopDocs topDocs = indexSearcher.search(query,1000000 ); Formatter formatter = new SimpleHTMLFormatter("<font color='red'>" ,"</font>" ); Scorer scorer = new QueryScorer(query); Highlighter highlighter = new Highlighter(formatter,scorer); for (int i=0 ;i<topDocs.scoreDocs.length;i++){ ScoreDoc scoreDoc = topDocs.scoreDocs[i]; int no = scoreDoc.doc; Document document = indexSearcher.doc(no); String highlighterContent = highlighter.getBestFragment(LuceneUtil.getAnalyzer(),"content" ,document.get("content" )); document.getField("content" ).setValue(highlighterContent); Article article = (Article) LuceneUtil.document2javabean(document,Article.class); articleList.add(article); } for (Article article : articleList){ System.out.println(article); } }
搜索结果摘要 如果我们搜索出来的文章内容太大了,而我们只想显示部分的内容,那么我们可以对其进行摘要…
值得注意的是:搜索结果摘要需要与设置高亮一起使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 String keywords = "钟福成" ; List<Article> articleList = new ArrayList<Article>(); QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content" ,LuceneUtil.getAnalyzer()); Query query = queryParser.parse(keywords); IndexSearcher indexSearcher = new IndexSearcher(LuceneUtil.getDirectory()); TopDocs topDocs = indexSearcher.search(query,1000000 ); Formatter formatter = new SimpleHTMLFormatter("<font color='red'>" ,"</font>" ); Scorer scorer = new QueryScorer(query); Highlighter highlighter = new Highlighter(formatter,scorer); Fragmenter fragmenter = new SimpleFragmenter(4 ); highlighter.setTextFragmenter(fragmenter); for (int i=0 ;i<topDocs.scoreDocs.length;i++){ ScoreDoc scoreDoc = topDocs.scoreDocs[i]; int no = scoreDoc.doc; Document document = indexSearcher.doc(no); String highlighterContent = highlighter.getBestFragment(LuceneUtil.getAnalyzer(),"content" ,document.get("content" )); document.getField("content" ).setValue(highlighterContent); Article article = (Article) LuceneUtil.document2javabean(document,Article.class); articleList.add(article); } for (Article article : articleList){ System.out.println(article); } }
搜索结果排序 我们搜索引擎肯定用得也不少,使用不同的搜索引擎来搜索相同的内容。他们首页的排行顺序也会不同…这就是它们内部用了搜索结果排序….
影响网页的排序有非常多种:
head/meta/【keywords关键字】
网页的标签整洁
网页执行速度
采用div+css
等等等等
而在Lucene中我们就可以设置相关度得分来使不同的结果对其进行排序:
1 2 3 4 5 IndexWriter indexWriter = new IndexWriter(LuceneUtil.getDirectory(),LuceneUtil.getAnalyzer(),LuceneUtil.getMaxFieldLength()); //为结果设置得分 document.setBoost(20F); indexWriter.addDocument(document); indexWriter.close();
当然了,我们也可以按单个字段排序:
1 2 3 Sort sort = new Sort(new SortField("id" ,SortField.INT,true )); TopDocs topDocs = indexSearcher.search(query,null ,1000000 ,sort);
也可以按多个字段排序:在多字段排序中,只有第一个字段排序结果相同时,第二个字段排序才有作用 提倡用数值型排序
1 2 Sort sort = new Sort(new SortField("count" ,SortField.INT,true ),new SortField("id" ,SortField.INT,true )); TopDocs topDocs = indexSearcher.search(query,null ,1000000 ,sort);
条件搜索 在我们的例子中,我们使用的是根据一个关键字来对某个字段的内容进行搜索。语法类似于下面:
1 QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content" ,LuceneUtil.getAnalyzer());
其实,我们也可以使用关键字来对多个字段进行搜索,也就是多条件搜索。我们实际中常常用到的是多条件搜索,多条件搜索可以使用我们最大限度匹配对应的数据 !
1 QueryParser queryParser = new MultiFieldQueryParser(LuceneUtil.getVersion(),new String[]{"content" ,"title" },LuceneUtil.getAnalyzer());
总结
Lucene是全文索引引擎的祖先 ,后面的Solr、Elasticsearch都是基于Lucene的(后面会有一篇讲Elasticsearch的,敬请期待~)
Lucene中存的就是一系列的二进制压缩文件和一些控制文件 ,这些内容统称为索引库 ,索引库又分了两个部分:词汇表、词汇表
了解索引库的优化方式:1、合并文件 2、设置内存索引库
Lucene的分词器有非常多种,选择自己适合的一种进行分词
查询出来的结果可对其设置高亮、摘要、排序