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

lucene3.0.3中的SpanNearQuery(一)

阅读更多

SpanNearQuery和PhraseQuery是差不多的意思,都是表示多个term必须全部存在且距离满足一定的条件的query,但是SpanTermQuery的用法更多,比如他有一个inorder的参数,可以控制多个term出现的位置是不是要符合指定的顺序(phraseQuery就是可以不按照出现的顺序的)

构建一个SpanNearQuery需要三个参数,一个是多个SpanQuery,一个是多个spanQuery之间最大的距离slop,第三个是是否要求多个term出现的位置和传入参数的顺序相同,他的构造方法为:

public SpanNearQuery(SpanQuery[] clauses, int slop, boolean inOrder) {
    this(clauses, slop, inOrder, true);
}

 我在这个博客中将多个SpanQuery的getSpans方法生成的Span叫做subSpan。看一下这个类的getSpans方法:

public Spans getSpans(final IndexReader reader) throws IOException {
	if (clauses.size() == 0) // optimize 0-clause case
		return new SpanOrQuery(getClauses()).getSpans(reader);
	if (clauses.size() == 1) // optimize 1-clause case
        	return clauses.get(0).getSpans(reader);
	return inOrder ? (Spans) new NearSpansOrdered(this, reader, collectPayloads) : (Spans) new NearSpansUnordered(this, reader);
}

 我们忽略clauses==0和1的情况(因为这两个没有意义),直接看最后一种,他会根据各个term在某个doc中出现的顺序要不要符合传入的各个subQuery(也就是clause中的query)的顺序返回不同的Span,如果是inorder,则只有存在所有的term且各个term出现的位置是按照sunQuery的顺序且他们之间的距离的和小于指定的slop的doc才能被召回,如果不是inorder,则只要全部出现且各个term之间的距离的和小于指定的slop的doc就能被召回。

 

我们看看NearSpansOrdered的实现,一切还是从next方法入手

/**和termSpan一样*/
@Override
public boolean next() throws IOException {
	if (firstTime) {//初次调用,将每一个sunSpan都调用next方法,即读取第一个位置
		firstTime = false;
		for (int i = 0; i < subSpans.length; i++) {
			if (!subSpans[i].next()) {
				more = false;
				return false;
			}
		}
		more = true;
	}
	if (collectPayloads) {//这个是为了收集payload设置的,如果是收集payload的话每一个位置都要收集所以把之前的payload清空,collectPayloads是一个list<byte[]>,用于收集所有的subSpan的payload,
		matchPayload.clear();
	}
	return advanceAfterOrdered();//关键是这个方法,他会将所有的subSpan都读取到同一个doc上,然后判断的当前的doc是否满足需求。
}

  

private boolean advanceAfterOrdered() throws IOException {
	//因为所有的span都必须满足,所以必须调到相等的doc上,即调用toSameDoc方法。toSameDoc方法和booleanQuery在and的情况下生成的ConjunctionSumScorer中将所有的子query调整到同一个doc上的算法是一样的,这里不再重复了,都是使用的循环数组
	while (more && (inSameDoc || toSameDoc())) {
		if (stretchToOrder() && shrinkToAfterShortestMatch()) {
			return true;
		}
	}
	return false; // no more matches
}

 指执行完toSameDoc之后所有的subSpan都停留在同一个doc上,接下来要判断下当前doc上各个term出现的顺序是不是符合置顶的subQuery的顺序,这个是通过stretchToOrder方法实现的 

private boolean stretchToOrder() throws IOException {
	matchDoc = subSpans[0].doc();
	for (int i = 1; inSameDoc && (i < subSpans.length); i++) {//每两个进行对比,i从1开始。
		while (!docSpansOrdered(subSpans[i - 1], subSpans[i])) {//docSpansOrdered用于判断当前两个位置符合不符合要求(即不能重叠且按照顺序出现)。如果不符合顺序要求,则读取当前的span(也就是第i个span)在当前doc上的下一个位置。
			//进入while表示当前term的当前位置是不符合顺序的,则要读取下一个位置(当前term在当前的doc上可能出现了多次)
			if (!subSpans[i].next()) {//如果当前的span(里面封装了termPosition)已经读取玩了,也就是所有的位置都读取完了,则返回false。
				inSameDoc = false;
				more = false;
				break;
			} else if (matchDoc != subSpans[i].doc()) {//读取下一个位置时已经到下一个doc了,表示当前的doc上的所有的位置已经读取玩了,则返回false。
				inSameDoc = false;
				break;
			}
		}
	}
	return inSameDoc;
}

 

 经过上面的stretchToOrder方法,如果返回是true的话表示当前的doc是符合顺序的,接下来判断各个term的距离的和是不是小于指定的值,用 shrinkToAfterShortestMatch()方法来完成

 

private boolean shrinkToAfterShortestMatch() throws IOException {		
	matchStart = subSpans[subSpans.length - 1].start();
	matchEnd = subSpans[subSpans.length - 1].end();
	Set<byte[]> possibleMatchPayloads = new HashSet<byte[]>();//paylaod最后的结果
	if (subSpans[subSpans.length - 1].isPayloadAvailable()) {//这里添加了最有一个span的payload,因为现在选择的最后一个span一定是正确的,距离之和最小的所有的位置一定是现在的最后一个位置,不会再移动,所以下面的for循环使用的是subSpans.length - 2,也就是说从后面向前计算。
		possibleMatchPayloads.addAll(subSpans[subSpans.length - 1].getPayload());
	}
	//这个叫做possible,是因为他可能是一个合格的payload,也可能不是
	Collection<byte[]> possiblePayload = null;
	int matchSlop = 0;
	int lastStart = matchStart;
	int lastEnd = matchEnd;
        //for循环的思路是确定了最后一个,然后再向前计算。
	for (int i = subSpans.length - 2; i >= 0; i--) {
		Spans prevSpans = subSpans[i];
		if (collectPayloads && prevSpans.isPayloadAvailable()) {//这里并没有更新payload,因为当前的位置可能并不是最合适的,可能后面还有一个位置更合适呢。
			Collection<byte[]> payload = prevSpans.getPayload();
			possiblePayload = new ArrayList<byte[]>(payload.size());
			possiblePayload.addAll(payload);
		}
		int prevStart = prevSpans.start();
		int prevEnd = prevSpans.end();
		//他的目的是计算最小的slop
		while (true) { // Advance prevSpans until after (lastStart, lastEnd)
			if (!prevSpans.next()) {//当前的span已经穷尽
				inSameDoc = false;
				more = false;
				break; // Check remaining subSpans for final match.
			} else if (matchDoc != prevSpans.doc()) {//当前的span没有穷尽doc,但是下一个doc已经不是当前的doc
				inSameDoc = false; // The last subSpans is not advanced here.
				break; // Check remaining subSpans for last match in this
						// document.
			} else {//出现了多次,并且当前不是最后一次。
				int ppStart = prevSpans.start();//新的位置的开始
				//新的位置的结束
				int ppEnd = prevSpans.end(); // Cannot avoid invoking .end()
				//判断新位置和下一个span的位置是不是符合顺序,新位置只会比刚才的位置更靠后,所以不用和前面的对比只需要和后面的对比即可。
				if (!docSpansOrdered(ppStart, ppEnd, lastStart, lastEnd)) {//不符合顺序,不用继续向后找
					break; // Check remaining subSpans.
				} else { // prevSpans still before (lastStart, lastEnd)  仍然符合顺序,则更新当前的span的匹配位置,使其更加靠后,从这里可以发现,他是优先使用最小的距离来计算slop。继续循环,因为可能后面还有出现的位置
					prevStart = ppStart;
					prevEnd = ppEnd;
					if (collectPayloads && prevSpans.isPayloadAvailable()) {//当前的位置比上一个位置更靠后,则重新读取此位置的payload。
						Collection<byte[]> payload = prevSpans.getPayload();
						possiblePayload = new ArrayList<byte[]>(payload.size());
						possiblePayload.addAll(payload);
					}
				}
			}
		}
		//添加最后确定的payload到最后的结果中
		if (collectPayloads && possiblePayload != null) {
			possibleMatchPayloads.addAll(possiblePayload);
		}
		assert prevStart <= matchStart;
		if (matchStart > prevEnd) {// Only non overlapping spans add to slop.  对于紧邻的term,是不算入slop的,因为matchStart-prevEnd=0,紧邻的意思是matchStart==prevEnd
			matchSlop += (matchStart - prevEnd);
		}
		/* Do not break on (matchSlop > allowedSlop) here to make sure that subSpans[0] is advanced after the match, if any. */
		matchStart = prevStart;
		lastStart = prevStart;
		lastEnd = prevEnd;
	}
		boolean match = matchSlop <= allowedSlop;
		if (collectPayloads && match && possibleMatchPayloads.size() > 0) {
		matchPayload.addAll(possibleMatchPayloads);
	}
	return match; // ordered and allowed slop
}

 

 

这样按照顺序的SpanNearQuery就完成了,他的思路是第一步把所有的span都指向到同一个doc上,然后找到最前面的符合顺序的一组,这样就定死了最后的一个span(即顺序是spans.size - 1的那个),然后按照逆序挨个移动其前面的span(即先移动第span.size-2个),移动到超过下一个span的位置,然后记录在移动的过程中出现的在下一个span的位置之前的最靠近的位置,这样挨个移动,就可以计算出距离最小的一组了。然后再移动到下一组,直到某个span在这个doc上已经没有匹配的位置了位置。

 

这个方法在我看来特别耗cpu资源,因为他的操作太多了,如果某个doc上的符合要求的term特别多,就更慢了,因为会每个位置都会读取一次匹配一次,尤其是当使用的sunQuery比较多或者是当某个域比较大的时候对cpu的更大,所以谨慎使用这个query。下一篇博客中我将写一下不按照顺序的SpanNearQuery。

分享到:
评论

相关推荐

    lucene-3.0.3-src.zip

    Lucene,作为Java领域中的一款强大而广泛使用的全文检索库,自诞生以来就备受开发者青睐。3.0.3版本是Lucene的一个重要里程碑,它在前代基础上进行了优化,提升了搜索性能,增强了稳定性。本文将对Lucene 3.0.3的...

    盘古分词、lucene3.0.3搜索的使用示例v1.3.zip

    《盘古分词与Lucene 3.0.3在.NET 4.0中的应用实践》 盘古分词和Lucene是两个在文本处理和信息检索领域中至关重要的工具。盘古分词,作为一款优秀的中文分词系统,能够高效准确地对中文文本进行分词,为后续的数据...

    lucene3.0.3搜索的使用示例

    这个"lucene3.0.3搜索的使用示例"压缩包文件很可能是为了帮助用户理解并学习如何在项目中应用Lucene 3.0.3版本的功能。 在Lucene 3.0.3中,主要包含了以下核心概念和知识点: 1. **索引(Indexing)**:这是Lucene...

    Lucene3.0.3+盘古分词 资源汇总

    《Lucene3.0.3与盘古分词:打造高效搜索引擎》 在信息技术日新月异的时代,搜索引擎已经成为我们获取信息的重要工具。Lucene,作为Apache软件基金会的一个开源项目,是Java语言实现的全文检索引擎库,为开发者提供...

    盘古分词、lucene3.0.3搜索的使用示例v1.2

    《盘古分词与Lucene 3.0.3在.NET 4.0中的应用实践》 盘古分词和Lucene是两个在中文信息处理领域广泛应用的工具,本示例将详细介绍如何在.NET 4.0环境中整合这两个组件,以实现高效的全文搜索功能,并新增了分页功能...

    盘古分词、lucene3.0.3搜索的使用示例.zip

    《盘古分词与Lucene 3.0.3在.NET 4.0中的应用实践》 盘古分词和Lucene是两个在文本处理和全文检索领域中至关重要的工具。本文将深入探讨如何在.NET 4.0环境中集成并使用这两个组件,以实现高效的文本分析和搜索引擎...

    Lucene3.0.3+盘古分词(证实可用,可指定使用自己的词库文件).rar

    在“Lucene3.0.3+盘古分词(证实可用,可指定使用自己的词库文件).rar”这个压缩包中,包含了实现这一功能所需的DLL文件和词库文件,这使得开发者可以轻松地在自己的项目中集成这一功能。 首先,我们要明白Lucene ...

    lucene 3.0.3.core.jar

    lucene3.0.3.core.jar文件,不用到apache官方网站下载17M的包,直接下载这个core就可以了。

    Lucene 3.0.3 API

    在 Lucene 3.0.3 版本中,我们看到了一系列重要的特性和改进,这些都极大地提升了其性能和易用性。 1. **索引过程**:Lucene 的索引过程涉及到将文档内容转换为倒排索引,这是一种高效的存储和检索方式。在 3.0.3 ...

    Lucene3.0分词系统.doc

    Lucene3.0分词系统的核心在于理解和应用其分词原理,无论是对于英文还是中文文本,这一过程都是构建高效搜索引擎的基础。以下是对Lucene3.0分词系统中涉及的关键知识点的深入解析。 ### 英文分词原理 英文分词相较...

    lucene-3.0.3.zip

    Lucene的主要目标是为开发者提供一个可嵌入到他们的应用中的、高效的全文检索引擎。这个版本的发布标志着Lucene在功能和稳定性上的进一步提升。 1. **全文检索基础** 全文检索是Lucene的核心功能,它能够从大量的...

    Lucene.NET v3.0.3 DEMO范例程序(含PanGu分词)

    这是Lucene.NET v3.0.3 DEMO范例程序(含PanGu分词),用C#语言编写的,同时对PanGu分词进行了整合,可以直接下载运行。 项目中还整理了一个后台任务线程监听范例,可以用作增量索引创建,但这个需要你自行加入相关...

    Lucene.net3.0.3源码

    Apache Lucene.Net 3.0.3 just passed a vote for release - our first official release since graduating from the incubator in August. A lot of work was put into porting and testing the code. We've ...

    lucene-smartcn-3.0.3.jar

    lucene-smartcn-3.0.3.jar

    Lucene SpellChecker3.0.2

    Lucene SpellChecker for Lucene 3.0.2

    Apache-Lucene.Net-3.0.3-RC2

    Lucene.net是Lucene的.net移植版本,是一个开源的全文检索引擎开发包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎。开发人员可以基于Lucene.net实现全文检索的...

    Lucene实战(中文版第二版)对应Lucene版本

    此资源对应的是Lucene 3.0.3版本,这是Lucene发展历史中的一个重要里程碑。 在Lucene 3.0.3版本中,包含了以下关键知识点: 1. **索引构建**:Lucene的核心功能之一就是快速构建倒排索引。这个版本中,你可以学习...

    Apache-Lucene.Net-3.0.3-RC2.bin

    3. **文档和字段**:在Lucene.Net中,每个要索引的实体被表示为一个文档,文档由多个字段组成,每个字段都有特定的属性,如是否存储、是否可搜索等。 4. **倒排索引**:Lucene.Net使用倒排索引来实现快速的全文搜索...

Global site tag (gtag.js) - Google Analytics