转: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是lucene用来排序的缓存。对要用来排序的字段,lucene会从索引中将每篇文档该字段的值都读出来,放到一个大小为maxDoc的数组中。maxDoc是lucene内部文档编号的最大值。有两点需要注意一下:
  • fieldCache中的字段值是从倒排表中读出来的,而不是从索引文件中存储的字段值,所以排序的字段必须是为设为索引字段
  • 用来排序的字段在索引的时候不能拆分(tokenized),因为fieldCache数组中,每个文档只对应一个字段值,拆分的话,cache中只会保存在词典中靠后的值。

fieldcache是lucene最占用的内存的部分,大部分内存溢出的错误都是由它而起,需要特别注意。

分组统计可以借用fieldCache来高效率的实现。调用lucene进行查询,通过读取倒排表并进行boolean运算,得到一个满足条件的文档的集合。通过每个结果文档号读取fieldCache数组中的值,并分不同的值累加数目,即可实现分组统计的功能。其中,如果某个字段对应多值,则在索引的时候不拆分,从filedCache数组读出后,再进行拆分统计。

对于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实现分组统计的方法相关推荐

  1. 利用bobo-browse 实现lucene的分组统计功能

    bobo-browse 是一用java写的lucene扩展组件,通过它可以很方便在lucene上实现分组统计功能. 可以从http://sna-projects.com/bobo/上下载和查看相关文档 ...

  2. spss分组统计的方法

    在做数据分析的时候,用的工具是excel,spss和oracle.因为对spss不了解,分组统计的活一直靠写sql,单调重复的工作让人烦躁,而且容易出错.后来发现spss有很好的分组统计功能,以二维数 ...

  3. R语言进行数据聚合统计(Aggregating transforms)计算滑动窗口统计值(Window Statistics):使用R原生方法、data.table、dplyr等方案、计算滑动分组统计

    R语言进行数据聚合统计(Aggregating transforms)计算滑动窗口统计值(Window Statistics):使用R原生方法.data.table.dplyr等方案.计算滑动分组统计 ...

  4. mysql十分钟分组_MYSQL每隔10分钟进行分组统计的实现方法

    前言 本文的内容主要是介绍了mysql每隔10分钟进行分组统计的实现方法,在画用户登录.操作情况在一天内的分布图时会非常有用,之前我只知道用「存储过程」实现的方法(虽然执行速度快,但真的是太不灵活了) ...

  5. 采用灰度分组统计方法实现图像中值滤波

    中值滤波是图像处理中常用的一种噪声滤波方法.传统的图像中值滤波代码采用排序方法实现,处理速度主要取决于排序算法,但无论什么排序算法,总离不开大量的元素比较.交换或移动,而这些恰好是当前计算机处理的&q ...

  6. MySQL分组统计及占比分析的方法实现

    分组统计及占比分析 题目背景: 统计各性别消费情况(字段包含性别.⼈数.⼈数占⽐.⼈均消费.消费⾦额.消费占⽐) 并以消费 占⽐降序. 这个题目咋一看,觉得非常简单,不就是一个分组就OK了吗,分组是没 ...

  7. java reduce 分组_使用JAVA8 stream中三个参数的reduce方法对List进行分组统计

    背景 平时在编写前端代码时,习惯使用lodash来编写'野生'的JavaScript; lodash提供来一套完整的API对JS对象(Array,Object,Collection等)进行操作,这其中 ...

  8. Python数据分析pandas之分组统计透视表

    Python数据分析pandas之分组统计透视表 数据聚合统计 Padans里的聚合统计即是应用分组的方法对数据框进行聚合统计,常见的有min(最小).max(最大).avg(平均值).sum(求和) ...

  9. mongodb按照时间分组统计

    使用spring data mongodb v1.8 需求1. 数据结构如下.说明:改集合记录的是公司各个系统的访问情况(localPath表示系统,requestTime 表示请求时间,字符串类型, ...

最新文章

  1. IIS+ASP+MySQL8.0+数据库连接解决方案(2019.7)
  2. 正则表达式的威力--轻松消除HTML代码
  3. 非对称加密 公钥私钥_选择Java加密算法第3部分–公钥/私钥非对称加密
  4. Wireshark工作笔记-对TCP连接与断开以及数据传输的实战分析
  5. Bug: tf.contrib.checkpoint.NoDependency object
  6. Jetpack—LiveData组件的缺陷以及应对策略 转至元数据结尾
  7. 保姆级教学,起点中文网字体反爬。
  8. 同济版《线性代数》再遭口诛笔伐,网友:它真的不太行
  9. 世界需要简化第四篇:从地面发射人造卫星或战术导弹,如何控制其运行轨道?——算法经高度抽象简化,所有下标经仔细核对
  10. 2018年春季学期《软件工程》教学总结
  11. outlook设置京东邮箱
  12. spdep | 如何在R语言中计算空间自相关指数
  13. python实现分词和词云制作
  14. qconshanghai2017
  15. 简单的图标移入效果(css缩放)
  16. 2022年全球与中国环己胺市场现状及未来发展趋势
  17. c语言注释符号 井号,读c语言深度剖析 -- 符号 注释符号
  18. JULIA学习材料合集
  19. Oracle用户管理和授权
  20. HBuilderX 使用内置终端打开命令框 操作文件

热门文章

  1. 2021/11/16 定时器Timer和cron表达式
  2. springboot实现简单的注册登录功能
  3. word2013总是出现未响应卡一下如何解决?
  4. 基于51单片机的停车场车位管理系统(程序+仿真+原理图+软件)
  5. 3月广州IT媒体杀人游戏比赛,就在本周五晚
  6. 学习笔记——C++实现ARP欺骗
  7. ASP.NET大型智能办公OA源码(带完整开发文档)
  8. Android中android:digits限制无效原因
  9. Linux学习笔记(二) 安装Fedora Linux
  10. 推荐系统的中的正排和倒排