上一篇写了SpanTermQuery,他没有任何意思,但是他有个子类,叫做PayloadTermQuery,这个类倒是可以实现一些功能。他也是根据term进行召回,但是对于得分的计算可以加入payload的信息,这样可以使得某个term和某个doc产生单独的关系,比如我们在电商搜索中,对于某个商品想做推广,使得他在搜索a词的时候得分特别大,但是其他的搜索的得分不会变大,显然使用boost是不正确的,因为他会使得所有的词的搜索得分都会变大。这个时候就可以使用payload了,这个类和SpanTermQuery的不同之处在于得分上,我们看一下这个类的得分的实现,在PayloadTermQuery最终生成的PayloadTermSpanScorer中,在调用nextDoc的时候会调用setFreqCurrentDoc方法,即计算当前doc的所有的位置的得分,源码如下:
/**
* 根据term在doc中的每一次出现计算得分,计算当前doc的freq值,相比spanTermQuery他的区别是添加了payload的处理
*/
@Override
protected boolean setFreqCurrentDoc() throws IOException {
if (!more) {
return false;
}
doc = spans.doc();
freq = 0.0f;
payloadScore = 0;
payloadsSeen = 0;
Similarity similarity1 = getSimilarity();
while (more && doc == spans.doc()) {
int matchLength = spans.end() - spans.start();
freq += similarity1.sloppyFreq(matchLength);//计算出现频率的得分
processPayload(similarity1);//计算payload的得分,比TermSpanQuery就是多了这个方法
more = spans.next();// this moves positions to the next match in this document
}
return more || (freq != 0);
}
最关键的就是多了一个processPayload的方法,他会读取当前位置的payload信息,然后根据payload计算一个得分,我们看一下这个方法的源码:
protected void processPayload(Similarity similarity) throws IOException {
if (positions.isPayloadAvailable()) {
payload = positions.getPayload(payload, 0);//读取payload
payloadScore = function.currentScore(doc, term.field(), spans.start(), spans.end(), payloadsSeen,
payloadScore, similarity.scorePayload(doc, term.field(), spans.start(), spans.end(),
payload, 0, positions.getPayloadLength()));//计算payload
payloadsSeen++;
} else {
// zero out the payload?
}
}
对于payload的计算会先根据similarity的scorePayload进行得分,默认的Similarity是直接返回1所以我们必须修改这个方法才可以,在计算完这个值之后还要调用一个function.currentScore方法,funtion是穿入的一个属性,用来计算当前的doc上所有的payload的得分,这个方法包括很多的参数,比如payloadSeen(当前的payload是第几个),paylaodScore(没有处理当前的payload时的得分),当前的得分。可以发现他是综合考量了所有的出现位置后计算的得分,但是这个得分并不是最终的得分,最终的得分是在score方法计算的。
@Override
public float score() throws IOException {
return includeSpanScore ? getSpanScore() * getPayloadScore() : getPayloadScore();
}
这里涉及到一个includeSpanScore,也就是不包括paylaod的得分(也就是和SpanTermQuery一样的得分),如果包括就两者相乘,不然就只计算PayLoad的得分,getPayloadScore的公式为:
protected float getPayloadScore() {
return function.docScore(doc, term.field(), payloadsSeen, payloadScore);
}
可以发现他是对之前计算的payloadScore又进行了一次计算,才算是最终的payload的得分。
通过上面的分析就能根据payload进行最终的得分的计算了,剩下的就是看PayloadFunction具体的实现了。
PayloadFunction是个抽象类,他有两个重要的方法,一个是 public abstract float currentScore(int docId, String field, int start, int end, int numPayloadsSeen, float currentScore, float currentPayloadScore);用来计算当前位置的payload的得分,一个是public abstract float docScore(int docId, String field, int numPayloadsSeen, float payloadScore);用于计算当前doc的所有的payLoad的得分,我们看一下他的具体实现类。
1、AveragePayloadFunction:对所有位置的payload的得分去平均数。
@Override
public float currentScore(int docId, String field, int start, int end, int numPayloadsSeen, float currentScore, float currentPayloadScore) {
return currentPayloadScore + currentScore;//将当前的位置的得分和之前的得分加起来,也就是计算所有位置的paylaod的和
}
@Override
public float docScore(int docId, String field, int numPayloadsSeen, float payloadScore) {
return numPayloadsSeen > 0 ? (payloadScore / numPayloadsSeen) : 1;//将和除以次数,计算平均分。
}
2、MaxPayloadFunction:对所有位置的payload取最大值
@Override
public float currentScore(int docId, String field, int start, int end, int numPayloadsSeen, float currentScore, float currentPayloadScore) {
if (numPayloadsSeen == 0) {
return currentPayloadScore;//第一次返回当前值
} else {
return Math.max(currentPayloadScore, currentScore);//返回所有的最大值
}
}
@Override
public float docScore(int docId, String field, int numPayloadsSeen, float payloadScore) {
return numPayloadsSeen > 0 ? payloadScore : 1;//返回最大值
}
3、MinPayloadFunction:对所有位置的payload取最小值
@Override
public float currentScore(int docId, String field, int start, int end, int numPayloadsSeen, float currentScore, float currentPayloadScore) {
if (numPayloadsSeen == 0) {
return currentPayloadScore;
} else {
return Math.min(currentPayloadScore, currentScore);
}
}
@Override
public float docScore(int docId, String field, int numPayloadsSeen, float payloadScore) {
return numPayloadsSeen > 0 ? payloadScore : 1;
}
当然我们也可以自己实现function。payload的功能写到这就算是完了。