用Lucene实现分组,facet功能,FieldCache

2019-03-27 01:12|来源: 网路

假如你像用lucene来作分组,比如按类别分组,这种功能,好了你压力大了,lucene本身是不支持分组的。

当你想要这个功能的时候,就可能会用到基于lucene的搜索引擎solr。

 

不过也可以通过编码通过FieldCache和单字段,对索引进行分组,比如:想构造类别树。大类里面还有小类那种。

这个功能实现起来可能会比较麻烦,主要是lucene提供的支持也不多,参考资料也不多。

(以下代码都是我在做测试的时候做的,可以稍作修改满足相应需求。)

 

//用于分组统计的对象GroupCollector 

import java.io.IOException;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Scorer;

public class GroupCollector extends Collector {

    private GroupField gf = new GroupField();// 保存分组统计结果
    private int docBase;
    // fieldCache
    private String[] fc;

    @Override
    public boolean acceptsDocsOutOfOrder() {
        return true;
    }

    @Override
    public void collect(int doc) throws IOException {
        // 因为doc是每个segment的文档编号,需要加上docBase才是总的文档编号
        final int docId = doc + this.docBase;
        // 添加的GroupField中,由GroupField负责统计每个不同值的数目
        this.gf.addValue(this.fc[docId]);

    }

    @Override
    public void setNextReader(IndexReader arg0, int arg1) throws IOException {
        this.docBase = this.docBase;

    }

    @Override
    public void setScorer(Scorer arg0) throws IOException {

    }

    public GroupField getGf() {
        return this.gf;
    }

    public void setGf(GroupField gf) {
        this.gf = gf;
    }

    public int getDocBase() {
        return this.docBase;
    }

    public void setDocBase(int docBase) {
        this.docBase = docBase;
    }

    public String[] getFc() {
        return this.fc;
    }

    public void setFc(String[] fc) {
        this.fc = fc;
    }

}

 



import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 用于保存分组统计后每个字段的分组结果
 */
public class GroupField {

    /**
     * 字段名
     */
    private String name;
    /**
     * 商品类型的对象列表
     */
    private List<SimpleCategory> values = new ArrayList<SimpleCategory>();
    /**
     * 保存字段值和文档个数的对应关系
     */
    private Map<String, Integer> countMap = new HashMap<String, Integer>();

    public Map<String, Integer> getCountMap() {
        return this.countMap;
    }

    public void setCountMap(Map<String, Integer> countMap) {
        this.countMap = countMap;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<SimpleCategory> getValues() {
        return this.values;
    }

    public void setValues(List<SimpleCategory> values) {
        this.values = values;
    }

    /**
     * 用于商品对象list的构造
     * 
     * @param value
     */
    public void addValue(String value) {
        if ((value == null) || "".equals(value)) return;
        // 对于多值的字段,支持按空格拆分
        final String[] temp = value.split(",");

        if (this.countMap.get(temp[1]) == null) {
            this.countMap.put(temp[1], 1);
            // 构造商品类型临时对象
            final SimpleCategory simpleCategory = new SimpleCategory();

            simpleCategory.setCategoryId(Integer.parseInt(temp[0]));
            simpleCategory.setCategoryName(temp[1]);
            simpleCategory.setParentId(Integer.parseInt(temp[2]));
            simpleCategory.setSortIndex(temp[3]);
            simpleCategory.setParentCategoryName(temp[4]);
            // simpleCategory.setAdImag(temp[5]);
            // simpleCategory.setParentAdImage(temp[6]);
            this.values.add(simpleCategory);
        }
        else {
            this.countMap.put(temp[1], this.countMap.get(temp[1]) + 1);
        }
        // for( String str : temp ){
        // if(countMap.get(str)==null){
        // countMap.put(str,1);
        // values.add(str);
        // }
        // else{
        // countMap.put(str, countMap.get(str)+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;
    // // }
    // }
}

 

自己构建想返回的对象


/**
 * 用于将lucene索引中的商品类型CategoryIndex字段,转换成商品类型的一个对象。
 * 
 * @author xiaozd
 * 
 */
public class SimpleCategory extends BaseModel {

    private static final long serialVersionUID = -2345212345526771266L;
    private int parentId;
    private int categoryId;
    private String categoryName;
    private String sortIndex;
    private int goodsCount;
    private String parentCategoryName;

    public int getParentId() {
        return this.parentId;
    }

    public void setParentId(int parentId) {
        this.parentId = parentId;
    }

    public int getCategoryId() {
        return this.categoryId;
    }

    public void setCategoryId(int categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return this.categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public String getSortIndex() {
        return this.sortIndex;
    }

    public void setSortIndex(String sortIndex) {
        this.sortIndex = sortIndex;
    }

    public static long getSerialversionuid() {
        return SimpleCategory.serialVersionUID;
    }

    public int getGoodsCount() {
        return this.goodsCount;
    }

    public void setGoodsCount(int goodsCount) {
        this.goodsCount = goodsCount;
    }

    public String getParentCategoryName() {
        return this.parentCategoryName;
    }

    public void setParentCategoryName(String parentCategoryName) {
        this.parentCategoryName = parentCategoryName;
    }

}

 

 

 

    /**
     * 查询商品的所有类型,方式:通过索引分组查询所有类型。
     *     @return    Map<String, String> 第一个参数表示商品类型id,第二个String表示商品类型名称
     */
    public List<SimpleCategory> getGoodsCategory() {
        
        List<SimpleCategory> values=new ArrayList<SimpleCategory>();
        try {
            
            IndexReader reader = IndexReader.open(FSDirectory.open(new File(luceneSearchPath)), true); // only searching, so read-only=true

            //读取"modified"字段值,放到fieldCache中
            final String[] fc=FieldCache.DEFAULT.getStrings(reader, "categoryIndex");
            IndexSearcher searcher = new IndexSearcher(reader);
            //GroupCollector是自定义文档收集器,用于实现分组统计
            GroupCollector myCollector=new GroupCollector();
            myCollector.setFc(fc);
            searcher.search(new MatchAllDocsQuery(), myCollector);
            //GroupField用来保存分组统计的结果
            GroupField gf=myCollector.getGf();
            values=gf.getValues();
            for (SimpleCategory value : values) {
                System.out.println("商品类型名称:  "+value +"  数量:"+gf.getCountMap().get(value.getCategoryName())+"   商品父类型名称: "+value.getParentCategoryName());
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return values;
    }

http://blog.csdn.net/xiaozhengdong/article/details/7035607


转自:http://www.cnblogs.com/chenying99/p/3819336

相关问答

更多
  • lucene怎么用[2022-07-03]

    Lucene是一个全文检索系统框架,开源的。 用起来比较方便,去Lucene的官网上下一个包并导入到你的工程中就可以调用包里面的类了。 一般的书里面介绍的版本都是1.4.3或者稍微高级一点的,不过现在lucene3.0正式发布,一些函数调用方法已经改变了,你可以下载一个版本低一点的Lucene比较适合学习~ 当然你直接从3.0入手的话网上资料也是非常丰富的~
  • 我对lucene只是略有了解,所以提点建议吧。 你说的分组统计,指的是分页,还是什么意思? 热门关键字的话,我觉得可以分词后,然后保存到数据库。 然后选择前几个次数最多的。
  • 分面查询可能就是您所追求的。 特别是范围分面查询。 Faceting queries may be what you are after. Specifically a range faceting query.
  • 如果您期望您的实体经常更改,则更容易索引ID,并且当您得到结果时,请在数据库中查找以获取颜色的名称。 这种方式改变颜色不会要求索引中更新受影响的文档。 在我们的系统中,我们将索引ID的Lucene而不是实体的名称,这正是由于您陈述的原因。 另外,我们的实体有一些与它们相关的属性,这些属性没有编入索引,所以我们必须打开数据库才能获取它们。 就性能而言,ID的面对将不会明显更慢或更快。 就数据库查询而言,这应该不是什么大问题,特别是如果你一次只能拉下数十个方面的话。 如果它成为一个问题,你总是可以使用缓存来加速 ...
  • 是的: http : //lucene.apache.org/core/4_10_4/grouping/org/apache/lucene/search/grouping/package-summary.html 但在较新的版本中,它只能在DocValues上工作,所以您必须再次将该字段添加为DocValue才能对其进行分组。 (但是也许在4.10中它仍然可以和FieldCache一起工作,但我对此并不熟悉) 您可以使用GroupingSearch或BlockGroupingCollector来为每个组Bl ...
  • 关于你的第一个问题,应用一个方面不会修改原始查询,它使用一个名为FacetCollector的自定义收集器 - 请参阅https://github.com/hibernate/hibernate-search/blob/master/engine/src/main/java/ org / hibernate / search / query / collector / impl / FacetCollector.java 。 在引擎盖下,收集器使用Lucene FieldCache进行构面计数。 多值分面的 ...
  • 在改变它时不应该有任何问题。 但请记住,当搜索结果很大但是方面计数不超过某个数字时,使用Bitsets (由Bitsets内部完成)会更Bitsets 。 (假设1000个方面10M命中) 如果你有更多的方面,但搜索结果不是很大,你可以迭代结果(在收集器中)并创建方面。 这样您可以获得更好的性能。 (说100K刻面1000次点击) 因此,2048可能是优化的数字,超过它可能导致性能损失。 No there should't be any issue in changing it. But remember ...
  • 在最近一篇名为“索引与类型”的官方Elasticsearch博客上的博客文章中,作者在选择是否想要使用多个索引或几种类型来建模数据时解决了一个常见的问题。 一个事实是Lucene指数不喜欢稀疏性。 结果,作者说 存在于一种类型中的字段也将消耗该字段不存在的类型的文档的资源。 [...]使用doc值时问题更严重:出于速度原因,doc值通常会为每个文档保留固定数量的磁盘空间,以便可以有效地处理这些值。 有一个Lucene问题旨在改善这种情况,已在5.4中修复,并将在Elasticsearch v2.2中提供。 ...