lucene实现分组统计的方法
转:http://www.cnblogs.com/huangfox/archive/2012/07/10/2584750.html
http://blog.163.com/liugangc@126/blog/static/20374821201011313238137/
1、工程目录
所谓分组统计,就是类似sql里group by的功能。在solr里,这个功能称为faceting。lucene本身不支持分组统计,不过可以使用fieldCache来实现分组统计功能,而且也有很好的性能。solr根据不同的情况,还提供了其他方法(filterCache和UnInvertedField)来实现,这个以后再说。
- fieldCache中的字段值是从倒排表中读出来的,而不是从索引文件中存储的字段值,所以排序的字段必须是为设为索引字段
- 用来排序的字段在索引的时候不能拆分(tokenized),因为fieldCache数组中,每个文档只对应一个字段值,拆分的话,cache中只会保存在词典中靠后的值。
fieldcache是lucene最占用的内存的部分,大部分内存溢出的错误都是由它而起,需要特别注意。
对于lucene的统计,我基本放弃使用factedSearch了,效率不高,而且两套索引总觉得有点臃肿!
这次我们通过改造Collector,实现简单的统计功能。经过测试,对几十万的统计还是比较快的。
首先我们简单理解下Collector在search中的使用情况!
Collector是一个接口,主要包括以下重要方法:
public abstract class Collector {//指定打分器public abstract void setScorer(Scorer scorer) throws IOException;//对目标结果进行收集,很重要!public abstract void collect(int doc) throws IOException;//一个索引可能会有多个子索引,这里相当于是对子索引的遍历操作public abstract void setNextReader(IndexReader reader, int docBase) throws IOException;//public abstract boolean acceptsDocsOutOfOrder();}
在search中我们来看看collector是怎么收集结果的!
public void search(Weight weight, Filter filter, Collector collector)throws IOException {// TODO: should we make this// threaded...? the Collector could be sync'd?// always use single thread:for (int i = 0; i < subReaders.length; i++) { // 检索每个子索引collector.setNextReader(subReaders[i], docBase + docStarts[i]);final Scorer scorer = (filter == null) ? weight.scorer(subReaders[i], !collector.acceptsDocsOutOfOrder(), true): FilteredQuery.getFilteredScorer(subReaders[i],getSimilarity(), weight, weight, filter);//构建打分器if (scorer != null) {scorer.score(collector);//打分}}}
scorer.score(collector)的过程如下:
public void score(Collector collector) throws IOException {collector.setScorer(this);int doc;while ((doc = nextDoc()) != NO_MORE_DOCS) {collector.collect(doc);//搜集结果}}
collector.collect(doc)的过程如下:
@Override
public void collect(int doc) throws IOException {float score = scorer.score();// This collector cannot handle these scores:assert score != Float.NEGATIVE_INFINITY;assert !Float.isNaN(score);totalHits++;if (score <= pqTop.score) {// 以下的实现使用了优先级队列,如果当前分值小于队列中pqTop.score则直接pass!return;}pqTop.doc = doc + docBase;pqTop.score = score;pqTop = pq.updateTop();
}
从上面这一坨坨代码我们可以大概看清collector在search中的应用情况。
那么统计呢?
首先我们来分析最简单的统计——“一维统计”,就只对一个字段的统计。例如统计图书每年的出版量、专利发明人发明专利数量的排行榜等。
统计的输入:检索式、统计字段
统计的输出:<统计项、数量>的集合
其中关键是我们怎么拿到统计项。这个又分成以下一种情况:
1)统计字段没有存储、不分词
我们可以使用FieldCache.DEFAULT.getStrings(reader, f);获取统计项。
2)统计字段没有存储、分词
需要通过唯一标识从数据库(如果正向信息存在数据库的话)取出统计项(字段内容),然后统计分析。可想而知效率极低。
3)统计字段存储、分词
可以通过doc.get(fieldName)取出统计项,依然比较低效
4)统计字段存储、不分词
和1)类似
因此我们如果要对某个字段进行统计,那么最好选用不分词(Index.NOT_ANALYZED),这个和排序字段的要求类似!
拿到统计项后,我们可以通过累加然后排序。(这里可以借助map)
下面给出主要代码:
package org.itat.collector;
import java.io.IOException;
import java.util.Arrays;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.Scorer;public class GroupCollectorDemo extends Collector {private GF gf = new GF();// 保存分组统计结果private String[] fc;// fieldCacheprivate String f;// 统计字段String spliter;int length;@Overridepublic void setScorer(Scorer scorer) throws IOException {}@Overridepublic void setNextReader(IndexReader reader, int docBase)throws IOException {//读取f的字段值,放入FieldCache中//在这里把所有文档的docid和它的f属性的值放入缓存中,以便获取fc = FieldCache.DEFAULT.getStrings(reader, f);System.out.println("fc:"+Arrays.toString(fc));/*** 先执行setNextReader方法再执行collect方法,* * 打印结果:* fc:[5611, 5611, 5611, 5611, 5611, 5611, 5611, 5611, 5611, 5611]*/}@Overridepublic void collect(int doc) throws IOException {//因为doc是每个segment的文档编号,需要加上docBase才是总的文档编号// 添加到GroupField中,由GroupField负责统计每个不同值的数目System.out.println(doc+"##"+doc+"##");gf.addValue(fc[doc]);/*** 打印结果:* 0##56111##56112##56113##56115##56116##56119##5611*/}@Overridepublic boolean acceptsDocsOutOfOrder() {return true;}public void setFc(String[] fc) {this.fc = fc;}public GF getGroupField() {return gf;}public void setSpliter(String spliter) {this.spliter = spliter;}public void setLength(int length) {this.length = length;}public void setF(String f) {this.f = f;}
}
package org.itat.collector;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class GF {// 所有可能的分组字段值,排序按每个字段值的文档个数大小排序private List<String> values = new ArrayList<String>();// 保存字段值和文档个数的对应关系private Map<String, Integer> countMap = new HashMap<String, Integer>();public List<String> getValues() {Collections.sort(values, new ValueComparator());return values;}public void addValue(String value) {if (value == null || "".equals(value))return;if (countMap.get(value) == null) {countMap.put(value, 1);values.add(value);} else {countMap.put(value, countMap.get(value) + 1);}}class ValueComparator implements Comparator<String> {public int compare(String value0, String value1) {if (countMap.get(value0) > countMap.get(value1)) {return -1;} else if (countMap.get(value0) < countMap.get(value1)) {return 1;}return 0;}}public void setValues(List<String> values) {this.values = values;}public Map<String, Integer> getCountMap() {return countMap;}public void setCountMap(Map<String, Integer> countMap) {this.countMap = countMap;}
}
package org.itat.collector;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Version;
import org.wltea.analyzer.lucene.IKAnalyzer;public class GroupTest {public static void main(String[] f) throws IOException, ParseException {FSDirectory dir = SimpleFSDirectory.open(new File("F:\\Workspaces\\collectortest\\index"));IndexReader reader = IndexReader.open(dir);IndexSearcher searcher = new IndexSearcher(reader);// GroupCollector是自定义文档收集器,用于实现分组统计String field = "content";//查询的字段String queryStr = "程序";//查询的内容QueryParser parser = new QueryParser(Version.LUCENE_35, field,new IKAnalyzer());long bt = System.currentTimeMillis();Query query = parser.parse(queryStr);System.out.println(query);GroupCollectorDemo myCollector = new GroupCollectorDemo();//classid是用来分组的字段,在查询后的结果中得到该字段的值然后进行分组统计//用来排序的字段在索引的时候不能拆分(tokenized),因为fieldCache数组中,//每个文档只对应一个字段值,拆分的话,cache中只会保存在词典中靠后的值。myCollector.setF("classid");searcher.search(query, myCollector);// GroupField用来保存分组统计的结果GF gf = myCollector.getGroupField();List<String> values = gf.getValues();long et = System.currentTimeMillis();System.out.println((et - bt) + "ms");for (int i = 0; i < values.size(); i++) {String value = values.get(i);System.out.println(value + "=" + gf.getCountMap().get(value));}}
}
下面的是转载,非上述代码的结论(本人注)
以上是对200多万数据的统计,而且是全数据统计。测试结果如下:
an:cn*
6616ms
毛裕民;谢毅=
13728
邱则有=
10126
杨孟君=
3771
王尔中=
1712
王信锁=
1658
张逶=
1314
朱炜=
1200
赵蕴岚;何唯平=
1039
杨贻方=
872
黄金富=
871
你可能会说——这不是坑爹吗?要6s的时间消耗!!!
解释:
1.数据量,统计的数据量在200万;
如果数据量在几十万,测试结果如下:
ad: 2006 *
213ms
邱则有= 1244
张云波= 628
赵蕴岚;何唯平= 398
余内逊;余谦梁= 376
杨贻方= 298
王尔中= 258
汪铁良= 224
赵发= 222
黄振华= 212
陆舟;于华章= 196
|
2.运行在pc机上;
以上解释也可以理解成借口,那么还有哪些环节可以优化呢?
从cpu和io来看,cpu应该主要是由于hashMap的操作引起的,io主要是由FieldCache.DEFAULT.getStrings(reader, f)获取统计项引起的。
如果高并发的情况下,io无疑是个大问题,我们可以考虑缓存。
对于运算量大的情况,我们可以考虑分布式。
后续我们将分析:
1)二维统计、多维统计
2)个性化统计
工程地址: http://download.csdn.net/detail/wxwzy738/5310947
lucene实现分组统计的方法相关推荐
- 利用bobo-browse 实现lucene的分组统计功能
bobo-browse 是一用java写的lucene扩展组件,通过它可以很方便在lucene上实现分组统计功能. 可以从http://sna-projects.com/bobo/上下载和查看相关文档 ...
- spss分组统计的方法
在做数据分析的时候,用的工具是excel,spss和oracle.因为对spss不了解,分组统计的活一直靠写sql,单调重复的工作让人烦躁,而且容易出错.后来发现spss有很好的分组统计功能,以二维数 ...
- R语言进行数据聚合统计(Aggregating transforms)计算滑动窗口统计值(Window Statistics):使用R原生方法、data.table、dplyr等方案、计算滑动分组统计
R语言进行数据聚合统计(Aggregating transforms)计算滑动窗口统计值(Window Statistics):使用R原生方法.data.table.dplyr等方案.计算滑动分组统计 ...
- mysql十分钟分组_MYSQL每隔10分钟进行分组统计的实现方法
前言 本文的内容主要是介绍了mysql每隔10分钟进行分组统计的实现方法,在画用户登录.操作情况在一天内的分布图时会非常有用,之前我只知道用「存储过程」实现的方法(虽然执行速度快,但真的是太不灵活了) ...
- 采用灰度分组统计方法实现图像中值滤波
中值滤波是图像处理中常用的一种噪声滤波方法.传统的图像中值滤波代码采用排序方法实现,处理速度主要取决于排序算法,但无论什么排序算法,总离不开大量的元素比较.交换或移动,而这些恰好是当前计算机处理的&q ...
- MySQL分组统计及占比分析的方法实现
分组统计及占比分析 题目背景: 统计各性别消费情况(字段包含性别.⼈数.⼈数占⽐.⼈均消费.消费⾦额.消费占⽐) 并以消费 占⽐降序. 这个题目咋一看,觉得非常简单,不就是一个分组就OK了吗,分组是没 ...
- java reduce 分组_使用JAVA8 stream中三个参数的reduce方法对List进行分组统计
背景 平时在编写前端代码时,习惯使用lodash来编写'野生'的JavaScript; lodash提供来一套完整的API对JS对象(Array,Object,Collection等)进行操作,这其中 ...
- Python数据分析pandas之分组统计透视表
Python数据分析pandas之分组统计透视表 数据聚合统计 Padans里的聚合统计即是应用分组的方法对数据框进行聚合统计,常见的有min(最小).max(最大).avg(平均值).sum(求和) ...
- mongodb按照时间分组统计
使用spring data mongodb v1.8 需求1. 数据结构如下.说明:改集合记录的是公司各个系统的访问情况(localPath表示系统,requestTime 表示请求时间,字符串类型, ...
最新文章
- IIS+ASP+MySQL8.0+数据库连接解决方案(2019.7)
- 正则表达式的威力--轻松消除HTML代码
- 非对称加密 公钥私钥_选择Java加密算法第3部分–公钥/私钥非对称加密
- Wireshark工作笔记-对TCP连接与断开以及数据传输的实战分析
- Bug: tf.contrib.checkpoint.NoDependency object
- Jetpack—LiveData组件的缺陷以及应对策略 转至元数据结尾
- 保姆级教学,起点中文网字体反爬。
- 同济版《线性代数》再遭口诛笔伐,网友:它真的不太行
- 世界需要简化第四篇:从地面发射人造卫星或战术导弹,如何控制其运行轨道?——算法经高度抽象简化,所有下标经仔细核对
- 2018年春季学期《软件工程》教学总结
- outlook设置京东邮箱
- spdep | 如何在R语言中计算空间自相关指数
- python实现分词和词云制作
- qconshanghai2017
- 简单的图标移入效果(css缩放)
- 2022年全球与中国环己胺市场现状及未来发展趋势
- c语言注释符号 井号,读c语言深度剖析 -- 符号 注释符号
- JULIA学习材料合集
- Oracle用户管理和授权
- HBuilderX 使用内置终端打开命令框 操作文件