`
suichangkele
  • 浏览: 200272 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

solr的facet源码解读(三)——facet.field之数字单值域类型

阅读更多

(这个使用的solr的版本是4.10)

承接上一篇文章,在对单值域的数字类型的域做facet的时候,会使用FCS方法,里面再调用的方法是NumericFacets.getCounts(searcher, base, field, offset, limit, mincount, missing, sort);所以看看这个的代码吧:

/**
 * 处理单值域的数字类型的facet
 * @param searcher
 * @param docs 		基础范围(即有q和fq确定的所有的doc的id)
 * @param fieldName	要facet的域的名字
 * @param offset	最后返回的结果的偏移量
 * @param limit		最后返回的结果的数量,小于0表示全部返回!
 * @param mincount	能够被facet的term值得最小的doc的数量,如果一个term匹配的doc的数量小于这个值,则不计算这个term。如果这个值位0且其他的term不能够满足条件,则要收集匹配的doc数量为0的term,即没有在上面的docs中doc的term。
 * @param missing	要不要返回null的值。即上面的
 * @param sort		收集到的值得排序
 * @return  
 * @throws IOException
 */
public static NamedList<Integer> getCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort) throws IOException {
	final boolean zeros = mincount <= 0;//要不要收集没有doc的值(包括没有在docs中的那些doc匹配的term)
	mincount = Math.max(mincount, 1);   //这么做是有好处的,这样可以加快速度。因为可能不需要使用不命中的那些值,单单那些已经命中的doc的term就已经可以得到结果了。
	final SchemaField sf = searcher.getSchema().getField(fieldName);//
	final FieldType ft = sf.getType();
	final NumericType numericType = ft.getNumericType();
	if (numericType == null) {//只能facet数字类型的
		throw new IllegalStateException();
	}
	final List<AtomicReaderContext> leaves = searcher.getIndexReader().leaves();
	
	// 1. 先把已经搜索到的docs的对应的值都收集了,收集到hashTable中。单独创建了这么一个类,用于保存term和匹配的doc的数量。
	final HashTable hashTable = new HashTable();
	final Iterator<AtomicReaderContext> ctxIt = leaves.iterator();
	AtomicReaderContext ctx = null;
	FieldCache.Longs longs = null;
	Bits docsWithField = null;
	int missingCount = 0;//
	for (DocIterator docsIt = docs.iterator(); docsIt.hasNext();) {//
		final int doc = docsIt.nextDoc();
		if (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()) {//找到这个doc所在的段
			do {
				ctx = ctxIt.next();
			} while (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc());
			//从fieldCache中获得这个doc的值,从之前的博客中可以知道,fieldCache也是优先获取docValue的值的,所以说这个收集方式就是优先使用docValue的值。
			switch (numericType) {
			case LONG:	longs = FieldCache.DEFAULT.getLongs(ctx.reader(), fieldName, true);	break;
			case INT:
				final FieldCache.Ints ints = FieldCache.DEFAULT.getInts(ctx.reader(), fieldName, true);
				longs = new FieldCache.Longs() {
					@Override
					public long get(int docID) {		return ints.get(docID);
					}
				};
				break;
			case FLOAT:
				final FieldCache.Floats floats = FieldCache.DEFAULT.getFloats(ctx.reader(), fieldName, true);
				longs = new FieldCache.Longs() {
					@Override
					public long get(int docID) {
						return NumericUtils.floatToSortableInt(floats.get(docID));
					}
				};
				break;
			case DOUBLE:
				final FieldCache.Doubles doubles = FieldCache.DEFAULT.getDoubles(ctx.reader(), fieldName, true);
				longs = new FieldCache.Longs() {
					@Override
					public long get(int docID) {
						return NumericUtils.doubleToSortableLong(doubles.get(docID));
					}
				};
				break;
			default:
				throw new AssertionError();
			}
			docsWithField = FieldCache.DEFAULT.getDocsWithField(ctx.reader(), fieldName);//含有这个域的doc的bit
		}
		long v = longs.get(doc - ctx.docBase);//获得这个id的值
		if (v != 0 || docsWithField.get(doc - ctx.docBase)) {//如果v != 0说明是一定有值得,但是==0的话可能也有值的,所以要判断两次。
			hashTable.add(doc, v, 1);//收集到了,加入到hash表里面。
		} else {
			++missingCount;//没有值,也就是null的数量,如果需要返回missing的话这个就有用了。
		}
	}
	// 2. 从hash表中根据规则 选择offset+limit个。
	final int pqSize = limit < 0 ? hashTable.size : Math.min(offset + limit, hashTable.size);,如果limit小于0 则全部的term都要返回,否则返回offset+ limit个。
	final PriorityQueue<Entry> pq;//根据排序创建一个优先队列
	if (FacetParams.FACET_SORT_COUNT.equals(sort) || FacetParams.FACET_SORT_COUNT_LEGACY.equals(sort)) {//如果排序是按照term匹配的doc数量排序
		pq = new PriorityQueue<Entry>(pqSize) {
			@Override
			protected boolean lessThan(Entry a, Entry b) {
				if (a.count < b.count || (a.count == b.count && a.bits > b.bits)) {//现根据count排序,如果count一样,按照数字排序
					return true;
				} else {
					return false;
				}
			}
		};
	} else {
		pq = new PriorityQueue<Entry>(pqSize) {
			@Override
			protected boolean lessThan(Entry a, Entry b) {//按照facet到的数字的大小排序
				return a.bits > b.bits;
			}
		};
	}
	Entry e = null;
	for (int i = 0; i < hashTable.bits.length; ++i) {//循环已经收集的term,这些的doc都是大于0的,因为他们的获取方式就是从已经搜索到的doc中获取的。
		if (hashTable.counts[i] >= mincount) {//如果大于指定的值,hashTable.counts[i]的这个值最小是1,所以如果这些的term已经够数量了,就不去查询词典表了,所以前面才将其置位最小是1的数字,当然如果指定了>1的数字,就使用那个数字
			if (e == null) {
				e = new Entry();
			}
			e.bits = hashTable.bits[i];
			e.count = hashTable.counts[i];
			e.docID = hashTable.docIDs[i];
			e = pq.insertWithOverflow(e);
		}
	}
	
	// 4. build the NamedList  构建最后的结果
	final ValueSource vs = ft.getValueSource(sf, null);//使用valueSource查询具体的值,因为之前查询的都是long类型的值,而我们要返回的是字符串,这次就是要查询字符串。
	final NamedList<Integer> result = new NamedList<>();
	
//	如果上面的term的数量不够,体现在两个方面,一个是排序,即收集的term的排序是按照term的字面值排序的,或者是minCount=0,表示要获得所有的term, 则要查询词典表,这就复杂了!
	// This stuff is complicated because if facet.mincount=0, the counts needs to be merged with terms from the terms dict(翻译过来是:如果mincount=0,则要读取词典表获得所有的term,因为现在仅仅是收集了一部分doc的term)
	// 或者不计算不命中的doc的term值或者是按照count排序的,就不需要查词典表了。
	if (!zeros || FacetParams.FACET_SORT_COUNT.equals(sort) || FacetParams.FACET_SORT_COUNT_LEGACY.equals(sort)) {
		final Deque<Entry> counts = new ArrayDeque<>();//保存offset后面的那些值
		while (pq.size() > offset) {//删除offset个到counts中去
			counts.addFirst(pq.pop());
		}
		// Entries from the PQ first, then using the terms dictionary
		for (Entry entry : counts) {
			final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves);
			final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx));//valueSource读取真正的值,使用FieldCache
			result.add(values.strVal(entry.docID - leaves.get(readerIdx).docBase), entry.count);//放入结果
		}

		//如果计算那些不命中的且单单使用docSet不够数量,则要查看词典表,即检查所有的term
		if (zeros && (limit < 0 || result.size() < limit)) { // need to merge with the term dict
			if (!sf.indexed()) {//此时必须要简历索引,不然没法查词典表了
				throw new IllegalStateException("Cannot use " + FacetParams.FACET_MINCOUNT + "=0 on field " + sf.getName() + " which is not indexed");
			}
			// Add zeros until there are limit results
			final Set<String> alreadySeen = new HashSet<>();
			//将使用docSet已经查找到的所有的值放入set集合里面,放置重复了
			while (pq.size() > 0) {//第一步是放入offset的那些
				Entry entry = pq.pop();
				final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves);
				final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx));
				alreadySeen.add(values.strVal(entry.docID - leaves.get(readerIdx).docBase));
			}
			//第二部是放入已经放入到result里面的那些
			for (int i = 0; i < result.size(); ++i) {
				alreadySeen.add(result.getName(i));
			}
			
			//获得这个域的所有的term
			final Terms terms = searcher.getAtomicReader().terms(fieldName);
			if (terms != null) {
				
				final String prefixStr = TrieField.getMainValuePrefix(ft);//这个域的前缀
				final BytesRef prefix;
				if (prefixStr != null) {
					prefix = new BytesRef(prefixStr);
				} else {
					prefix = new BytesRef();
				}
				
				final TermsEnum termsEnum = terms.iterator(null);
				BytesRef term;
				switch (termsEnum.seekCeil(prefix)) {
				case FOUND:
				case NOT_FOUND:
					term = termsEnum.term();
					break;
				case END:
					term = null;
					break;
				default:
					throw new AssertionError();
				}
				
				final CharsRef spare = new CharsRef();

				
				//继续跳过offset-hashtable.size,因为这一部分不要。
				for (int skipped = hashTable.size; skipped < offset && term != null	&& StringHelper.startsWith(term, prefix);) {
					ft.indexedToReadable(term, spare);
					final String termStr = spare.toString();
					if (!alreadySeen.contains(termStr)) {
						++skipped;
					}
					term = termsEnum.next();
				}
				
				
				
				//读取limit-result.size个term
				for (; term != null && StringHelper.startsWith(term, prefix) && (limit < 0 || result.size() < limit); term = termsEnum.next()) {
					ft.indexedToReadable(term, spare);
					final String termStr = spare.toString();
					if (!alreadySeen.contains(termStr)) {//如果从来没有出现过!
						result.add(termStr, 0);//添加到结果中
					}
				}	
			}
		}
	} else {//收集docset中没有的且按照字面值排序,读取词典表
		// sort=index, mincount=0 and we have less than limit items => Merge the PQ and the terms dictionary on the fly
		if (!sf.indexed()) {
			throw new IllegalStateException("Cannot use " + FacetParams.FACET_SORT + "=" + FacetParams.FACET_SORT_INDEX + " on a field which is not indexed");
		}
		//key是facet的数字的字面值,value是次数
		final Map<String, Integer> counts = new HashMap<>();
		while (pq.size() > 0) {//从优先队列里面取出来,再放入到counts里面,放入的key是字面值,value是在docSet中facet到的次数
			final Entry entry = pq.pop();
			final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves);
			final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx));
			counts.put(values.strVal(entry.docID - leaves.get(readerIdx).docBase), entry.count);
		}
		final Terms terms = searcher.getAtomicReader().terms(fieldName);
		if (terms != null) {
			final String prefixStr = TrieField.getMainValuePrefix(ft);
			final BytesRef prefix;
			if (prefixStr != null) {
				prefix = new BytesRef(prefixStr);
			} else {
				prefix = new BytesRef();
			}
			final TermsEnum termsEnum = terms.iterator(null);
			BytesRef term;
			switch (termsEnum.seekCeil(prefix)) {
			case FOUND:
			case NOT_FOUND:
				term = termsEnum.term();
				break;
			case END:
				term = null;
				break;
			default:
				throw new AssertionError();
			}
			final CharsRef spare = new CharsRef();
			for (int i = 0; i < offset && term != null && StringHelper.startsWith(term, prefix); ++i) {//
				term = termsEnum.next();
			}
			for (; term != null && StringHelper.startsWith(term, prefix)
					&& (limit < 0 || result.size() < limit); term = termsEnum.next()) {
				ft.indexedToReadable(term, spare);
				final String termStr = spare.toString();
				Integer count = counts.get(termStr);
				if (count == null) {
					count = 0;
				}
				result.add(termStr, count);
			}
		}
	}

	if (missing) {//添加null的值得数量
		result.add(null, missingCount);
	}
	return result;
}

 从上面可以总结出经验来,在对单值域的数字类型的域做facet的时候,最好是设置上mincount>0,且按照doc的数量排序,在这个时候仅仅是使用命中的所有的doc的term做聚合,数量较少,不会有其他的操作; 否则会读取词典表,导致效率低下。还需要注意的是,上面的所有的操作都是在一个线程中完成的,之前说的多线程是在多个facet.field的情况下才会使用的。

 

 

分享到:
评论

相关推荐

    solrj的facet查询总结

    Solr 的默认 requestHandler(org.apache.solr.handler.component.SearchHandler)已经包含了 Facet 组件(org.apache.solr.handler.component.FacetComponent)。如果自定义 requestHandler 或者对默认的 ...

    solr facet 笔记

    Solr 提供了多种 Facet 组件,包括 `facet.field` 用于基于字段的 Faceting,`facet.query` 用于自定义查询的 Faceting,以及 `facet.date` 用于日期的 Faceting。这些组件可以通过配置在查询请求中指定,以满足不同...

    solr-6.2.0源码

    三、Solr 6.2.0的改进与新特性 1. 改进的ShardHandler API:增强了对请求的并发处理能力,提高了性能。 2. 引入了新的查询执行模型(Distributed Searcher):优化了分布式查询的性能和稳定性。 3. 增强的CSV处理:...

    solr(solr-9.0.0-src.tgz)源码

    - **配置**:Solr的配置文件在`conf`目录下,包括schema.xml(定义字段和字段类型)、solrconfig.xml(配置索引和查询行为)等。 - **集合与分片**:在分布式环境中,Solr将数据分为多个集合,每个集合可以进一步...

    solr-mongo-importer-1.1.0.jar

    solr-mongo-importer-1.1.0.jar solr-mongo-importer-1.1.0.jar solr-mongo-importer-1.1.0.jar

    solr.war包solr.war包solr.war包solr.war包solr.war包

    solr.warsolr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包...

    Java后端学习路线梳理(csdn)————程序.pdf

    搜索引擎如ElasticSearch和Solr,以及分布式和微服务相关技术(服务发现/注册、网关、服务调用、熔断/降级、配置中心、认证鉴权、分布式事务、任务调度、链路追踪和监控、日志分析和监控)也是必备技能。 最后,...

    快速上手数据挖掘之solr搜索引擎高级教程(Solr集群、KI分词)第15讲 solr之Facet 共7页.pptx

    Solr集群安装与配置(一)第11讲 Solr集群安装与配置(二)第12讲 SolrCloud基本概念第13讲 Solrj操作SolrCloud第14讲 solr索引主从同步第15讲 solr之Facet第16讲 solr之FacetPivot第17讲 solr之Group第18讲 solr之...

    java进阶Solr从基础到实战

    Solr它是一种开放源码的、基于 Lucene 的搜索服务器,可以高效的完成全文检索的功能。在本套课程中,我们将全面的讲解Solr,从Solr基础到Solr高级,再到项目实战,基本上涵盖了Solr中所有的知识点。 主讲内容 章节一...

    solr6.6.0源码

    这个源码包包含了 Solr 的所有源代码,对于理解 Solr 的工作原理、进行二次开发或者定制化配置有着非常重要的意义。接下来,我们将深入探讨 Solr 6.6.0 中的一些关键知识点。 一、Solr 架构与组件 Solr 的核心架构...

    Solr项目源码及solr资源包

    Solr项目源码及solr资源包是一个针对搜索引擎平台Apache Solr的学习与实践资源集合,主要结合了Spring Data Solr框架进行操作。这个项目旨在帮助开发者更好地理解和运用Solr进行数据索引和检索。让我们详细地探讨...

    apache-solr-4.0.0-ALPHA-src.gz官方包

    2. **Lucene 库**:Solr构建在Lucene之上,Lucene是Java的一个开源全文检索库。4.0版本可能使用了Lucene的较新版本,学习源码可以帮助理解Lucene如何支持Solr的全文搜索、高亮显示、拼写检查等功能。 3. **配置文件...

    Solr in action.mobi

    PART 1 MEET SOLR. .................................................................1 1 ■ Introduction to Solr 3 2 ■ Getting to know Solr 26 3 ■ Key Solr concepts 48 4 ■ Configuring Solr 82 5 ■ ...

    solr 4.10源码

    这个源码包包含了Solr 4.10.4的所有源代码,对于开发者来说,这是一个深入了解Solr工作原理、定制功能以及进行二次开发的重要资源。 1. **Solr简介** Apache Solr是一个基于Lucene的全文检索服务,提供了一个高效...

    配置好的solr启动环境

    Solr,全称为Apache Solr,是一款开源的全文搜索引擎,被广泛应用于企业级搜索解决方案中。它基于Java,能够高效地处理大量数据的检索、排序和过滤等任务。在这个“配置好的solr启动环境”中,我们有一个预先配置好...

    apache-solr-dataimporthandler-extras-1.4.0.jar.zip

    在给定的压缩包“apache-solr-dataimporthandler-extras-1.4.0.jar.zip”中,主要包含了一个名为“apache-solr-dataimporthandler-extras-1.4.0.jar”的文件,这个文件是Solr的一个重要组件——DataImportHandler...

    solr-5.2.1.part1.rar 编译第1部分,共2部分

    把solr.war(solr-4.2.0\example\solr-webapp\solr.war)里的东西全复制到WebRoot下 2. 创建solr/home, 把solr-4.2.0\example\solr所有文件复制到你创建的solr/home目录下 3. 创建JNDI让程序找到solr/home(当然你也...

    solr-dataimport-scheduler.jar 可使用于solr7.x版本

    Solr 数据导入调度器(solr-dataimport-scheduler.jar)是一个专门为Apache Solr 7.x版本设计的组件,用于实现数据的定期索引更新。在理解这个知识点之前,我们需要先了解Solr的基本概念以及数据导入处理...

    bhl_rails_solr-源码.rar

    3. **Solr配置**:源码中可能包含针对不同环境(如开发、测试、生产)的Solr配置,通过solr.yml管理。这些配置可能涉及Solr服务器地址、端口、数据目录等。 4. **错误处理**:在与Solr通信过程中,可能会遇到网络...

    ik-analyzer-solr5-5.x.jar

    中文分词器ik-analyzer-solr5-5.x.jar,已经打包好,直接用就可以 2积分不黑心

Global site tag (gtag.js) - Google Analytics