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

lucene3.0.3中的Spanquery和Spans介绍

阅读更多

      SpanQuery就是用来查询不仅仅是含有term,并且存在的各个term的位置符合一定条件的doc,从源码上讲他最大的改变就是有了getSpans(IndexReader reader)方法,该方法返回的是Spans,在SpanQuery中使用Spans这个类来召回doc(在普通的termQuery中是使用termDocs进行召回),我们先看看Spans这个类。

      在Spans的javadoc中有这样的说明:Spans用于枚举term出现的位置,如果是在一个document中则枚举在当前的doc中出现的各个位置,枚举完了当前的doc的所有的位置,则会读取下一个doc,这正是他的next()方法的意义,多次调用这个next方法可能会修改当前的docid(使用doc方法)也可能不会,因为当前的term在当前的doc中可能出现了多次。Spans还有一个方法start()用于获得当前的位置的开始位置(也就是在创建索引时的位置增量),还有end()方法,用于获得当前的位置的结束位置。getPayload()方法,该方法用于获得当前位置的payload,当然可能在建立索引的时候就没有保存词频和位置信息,所以还有一个开关——isPayloadAvailable()方法,他表示当前的位置能否读取payload。注意这里返回的是Collection<byte[]>类型的payload,这是因为可能有的Spans的子类(比如后面说的SpanNearQuery生成的Span,包含多个子Spans)会返回多个payload,所以用一个集合Collection,一个paylaod用byte[]表示。一句话总结下Spans:用于按照顺序枚举一个term在所有包含他的doc中的位置,可以读取payload。

 

       我们看一个最简单的SpanQuery——SpanTermQuery。

      SpanTermQuery其实本质上没有任何用处,他的作用也是按照term进行查找doc(和TermQuery是一个意思),只要一个doc含有指定的term就会被召回,没有任何其他的限制(和普通的termQuery的召回逻辑一样),我觉得它存在的意义仅仅是让我们接触一下SpanQuery。他有了区别于普通的termQuery的地方——使用termPositions而不是termDocs进行查找(体现在使用Spans而没有直接在query中使用Term),打分方式也发生了变化——计算tf与termQuery完全不一样;第二个意义是在他的基础上有一个PayLoadTermQuery,这个类可以使用payload进行打分。我们看一下SpanTermQuery的源码。

      先看一下他最关键的方法:getSpans方法:

@Override
public Spans getSpans(final IndexReader reader) throws IOException {
	return new TermSpans(reader.termPositions(term), term);//返回的是一个TermSpans
}

 我们看一下TermSpans,尤其关注其next方法:

 

public TermSpans(TermPositions positions, Term term) throws IOException {
	this.positions = positions;
	this.term = term;
	doc = -1;
}

 在构造方法中会传入一个termPositions,这个类是TermDocs的子类,他有一个额外功能是能读取prx文件,也就是能得到每一个term在每一个doc中的出现的位置以及在该位置上的payload,当然他也能读取倒排表,也就是能召回doc。我们看一下termSpan的next方法:

/**
 * 如果当前的term在当前的doc中的所有的出现位置读取完了(count==freq)则继续读取下一个doc,然后读取下一个位置,增加count++; 
 * 如果没有读取完,则仅仅读取下一个位置。
 * */
@Override
public boolean next() throws IOException {当前的doc的所有的位置都读取完了。
	if (count == freq) {//
		if (!positions.next()) {
			doc = Integer.MAX_VALUE;
			return false;
		}
		doc = positions.doc();
		freq = positions.freq();
		count = 0;
	}
	//读取下一个位置,此时一定返回true,因为当前的doc没有读取完。
	position = positions.nextPosition();
	count++;
	return true;
}

 这里的count表示在当前的doc上所有的出现的次数(用freq表示)中已经读取了多少次,如果已经读取完了当前的doc的所有的次数,则读取下一个doc,即进入if判断,否则继续读取当前的doc。

 

SpanTermQuery的weight以及scorer

      SpanTermQuery并没有改写createWeight方法,他会生成一个SpanWeight,这个类没有什么特殊的,他会继续生成一个SpanScorer用来回收doc和打分,重点看一下SpanScorer的nextDoc方法,因为这个方法用于回收doc,这个方法会调用setFreqCurrentDoc方法,

/**
 * 设置当前doc的freq,他是通过读取当前doc的多个位置来实现的。
 */
protected boolean setFreqCurrentDoc() throws IOException {
	if (!more) {
	        return false;
	}
	doc = spans.doc();//通过Spans获得当前的doc(当然spans是根据其封装的termPosition来实现的这个功能)
	freq = 0.0f;
	do {
		int matchLength = spans.end() - spans.start();
		freq += getSimilarity().sloppyFreq(matchLength);
		more = spans.next();
	} while (more && (doc == spans.doc()));//直到读取完当前doc上所有的position,所以尽管spans的next方法只会读取一个位置,但是在scorer里面试读取了一个完整的doc,并且每个位置都读取了,
	return true;
}	

 scorer利用spans了读取了一个完整的doc,并且根据出现的次数得出了freq的值,在score方法中就会使用这个值对当前的doc进行打分。

/**得分唯一不同的是计算tf发生了变化*/
@Override
public float score() throws IOException {
	float raw = getSimilarity().tf(freq) * value; // raw score
	return norms == null ? raw : raw * Similarity.decodeNorm(norms[doc]); // normalize
}

 

这样SpanTermQuery就完了,他也是根据term进行召回doc,只不过他是将倒排表(termDocs,在SpanQuery中是使用了TermPositions)封装在了Spans里面,使用spans来一个一个位置的读取,并且得分的计算方式发生了变化。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

     

      

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics