文档章节

Lucene5学习之FunctionQuery功能查询

小致dad
 小致dad
发布于 2017/07/21 21:32
字数 1876
阅读 50
收藏 0

 我猜,大家最大的疑问就是:不是已经有那么多Query实现类吗,为什么又设计一个FunctionQuery,它的设计初衷是什么,或者说它是用来解决什么问题的?我们还是来看看源码里是怎么解释FunctionQuery的:

意思就是基于ValueSource来返回每个文档的评分即valueSourceScore,那ValueSource又是怎么东东?接着看看ValueSource源码里的注释说明:

 ValueSource是用来根据指定的IndexReader来实例化FunctionValues的,那FunctionValues又是啥?

 从接口中定义的函数可以了解到,FunctionValues提供了根据文档ID获取各种类型的DocValuesField域的值的方法,那这些接口返回的域值用来干嘛的,翻看FunctionQuery源码,你会发现:

从上面几张图,我们会发现,FunctionQuery构造的时候需要提供一个ValueSource,然后在FunctionQuery的内部类AllScorer中通过valueSource实例化了FunctionValues,然后在计算FunctionQuery评分的时候通过FunctionValues获取DocValuesField的域值,域值和FunctionQuery的权重值相乘得到FunctionQuery的评分。

float score = qWeight * vals.floatVal(doc);

 那这里ValueSource又起什么作用呢,为什么不直接让FunctionQuery来构建FunctionValues,而是要引入一个中间角色ValueSource呢?

因为FunctionQuery应该线程安全的,即允许多次查询共用同一个FunctionQuery实例,如果让FunctionValues直接依赖FunctionQuery,那可能会导致某个线程通过FunctionValues得到的docValuesField域值被另一个线程修改了,所以引入了一个ValuesSource,让每个FunctionQuery对应一个ValueSource,再让ValueSource去生成FunctionValues,因为docValuesField域值的正确性会影响到最后的评分。另外出于缓存原因,因为每次通过FunctionValues去加载docValuesField的域值,其实还是通过IndexReader去读取的,这就意味着有磁盘IO行为,磁盘IO次数可是程序性能杀手哦,所以设计CachingDoubleValueSource来包装ValueSource.不过CachingDoubleValueSource貌似还处在捐献模块,不知道下个版本是否会考虑为ValueSource添加Cache功能。

ValueSource构造很简单:

public DoubleFieldSource(String field) {  
    super(field);  
  } 

你只需要提供一个域的名称即可,不过要注意,这里的域必须是DocValuesField,不能是普通的StringField,TextField,IntField,FloatField,LongField。

那FunctionQuery可以用来解决什么问题?举个例子:比如你索引了N件商品,你希望通过某个关键字搜索时,出来的结果优先按最近上架的商品显示,再按商品和搜索关键字匹配度高低降序显示,即你希望最近上架的优先靠前显示,评分高的靠前显示。

下面是一个FunctionQuery使用示例,模拟类似这样的场景:

书籍的出版日期越久远,其权重因子会按天数一天天衰减,从而实现让新书自动靠前显示

import java.io.IOException;  
import java.util.Map;  
  
import org.apache.lucene.index.DocValues;  
import org.apache.lucene.index.LeafReaderContext;  
import org.apache.lucene.index.NumericDocValues;  
import org.apache.lucene.queries.function.FunctionValues;  
import org.apache.lucene.queries.function.valuesource.FieldCacheSource;  
  
import com.yida.framework.lucene5.util.score.ScoreUtils;  
  
/** 
 * 自定义ValueSource[计算日期递减时的权重因子,日期越近权重值越高] 
 * @author Lanxiaowei 
 * 
 */  
public class DateDampingValueSouce extends FieldCacheSource {  
    //当前时间  
    private static long now;  
    public DateDampingValueSouce(String field) {  
        super(field);  
        //初始化当前时间  
        now = System.currentTimeMillis();  
    }  
    /** 
     * 这里Map里存的是IndexSeacher,context.get("searcher");获取 
     */  
    @Override  
    public FunctionValues getValues(Map context, LeafReaderContext leafReaderContext)  
            throws IOException {  
        final NumericDocValues numericDocValues = DocValues.getNumeric(leafReaderContext.reader(), field);    
        return new FunctionValues() {  
            @Override  
            public float floatVal(int doc) {  
                return ScoreUtils.getNewsScoreFactor(now, numericDocValues,doc);  
            }  
            @Override  
            public int intVal(int doc) {  
                return (int) ScoreUtils.getNewsScoreFactor(now, numericDocValues,doc);  
            }  
            @Override  
            public String toString(int doc) {  
                return description() + '=' + intVal(doc);  
            }  
        };  
    }  
      
}  
import org.apache.lucene.index.NumericDocValues;  
  
import com.yida.framework.lucene5.util.Constans;  
  
/** 
 * 计算衰减因子[按天为单位] 
 * @author Lanxiaowei 
 * 
 */  
public class ScoreUtils {  
    /**存储衰减因子-按天为单位*/  
    private static float[] daysDampingFactor = new float[120];  
    /**降级阀值*/  
    private static float demoteboost = 0.9f;  
    static {  
        daysDampingFactor[0] = 1;  
        //第一周时权重降级处理  
        for (int i = 1; i < 7; i++) {  
            daysDampingFactor[i] = daysDampingFactor[i - 1] * demoteboost;  
        }  
        //第二周  
        for (int i = 7; i < 31; i++) {             
            daysDampingFactor[i] = daysDampingFactor[i / 7 * 7 - 1]  
                    * demoteboost;  
        }  
        //第三周以后  
        for (int i = 31; i < daysDampingFactor.length; i++) {  
            daysDampingFactor[i] = daysDampingFactor[i / 31 * 31 - 1]  
                    * demoteboost;  
        }  
    }  
      
    //根据相差天数获取当前的权重衰减因子  
    private static float dayDamping(int delta) {  
        float factor = delta < daysDampingFactor.length ? daysDampingFactor[delta]  
                : daysDampingFactor[daysDampingFactor.length - 1];  
        System.out.println("delta:" + delta + "-->" + "factor:" + factor);  
        return factor;  
    }  
      
    public static float getNewsScoreFactor(long now, NumericDocValues numericDocValues, int docId) {  
        long time = numericDocValues.get(docId);  
        float factor = 1;  
        int day = (int) (time / Constans.DAY_MILLIS);  
        int nowDay = (int) (now / Constans.DAY_MILLIS);  
        System.out.println(day + ":" + nowDay + ":" + (nowDay - day));  
        // 如果提供的日期比当前日期小,则计算相差天数,传入dayDamping计算日期衰减因子  
        if (day < nowDay) {  
            factor = dayDamping(nowDay - day);  
        } else if (day > nowDay) {  
            //如果提供的日期比当前日期还大即提供的是未来的日期  
            factor = Float.MIN_VALUE;  
        } else if (now - time <= Constans.HALF_HOUR_MILLIS && now >= time) {  
            //如果两者是同一天且提供的日期是过去半小时之内的,则权重因子乘以2  
            factor = 2;  
        }  
        return factor;  
    }  
      
    public static float getNewsScoreFactor(long now, long time) {  
        float factor = 1;  
        int day = (int) (time / Constans.DAY_MILLIS);  
        int nowDay = (int) (now / Constans.DAY_MILLIS);  
        // 如果提供的日期比当前日期小,则计算相差天数,传入dayDamping计算日期衰减因子  
        if (day < nowDay) {  
            factor = dayDamping(nowDay - day);  
        } else if (day > nowDay) {  
            //如果提供的日期比当前日期还大即提供的是未来的日期  
            factor = Float.MIN_VALUE;  
        } else if (now - time <= Constans.HALF_HOUR_MILLIS && now >= time) {  
            //如果两者是同一天且提供的日期是过去半小时之内的,则权重因子乘以2  
            factor = 2;  
        }  
        return factor;  
    }  
    public static float getNewsScoreFactor(long time) {  
        long now = System.currentTimeMillis();  
        return getNewsScoreFactor(now, time);  
    }  
}  
import java.io.IOException;  
import java.nio.file.Paths;  
import java.text.DateFormat;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
import org.apache.lucene.analysis.Analyzer;  
import org.apache.lucene.analysis.standard.StandardAnalyzer;  
import org.apache.lucene.document.Document;  
import org.apache.lucene.document.Field;  
import org.apache.lucene.document.Field.Store;  
import org.apache.lucene.document.LongField;  
import org.apache.lucene.document.NumericDocValuesField;  
import org.apache.lucene.document.TextField;  
import org.apache.lucene.index.DirectoryReader;  
import org.apache.lucene.index.IndexReader;  
import org.apache.lucene.index.IndexWriter;  
import org.apache.lucene.index.IndexWriterConfig;  
import org.apache.lucene.index.IndexWriterConfig.OpenMode;  
import org.apache.lucene.index.Term;  
import org.apache.lucene.queries.CustomScoreQuery;  
import org.apache.lucene.queries.function.FunctionQuery;  
import org.apache.lucene.search.IndexSearcher;  
import org.apache.lucene.search.ScoreDoc;  
import org.apache.lucene.search.Sort;  
import org.apache.lucene.search.SortField;  
import org.apache.lucene.search.TermQuery;  
import org.apache.lucene.search.TopDocs;  
import org.apache.lucene.store.Directory;  
import org.apache.lucene.store.FSDirectory;  
/** 
 * FunctionQuery测试 
 * @author Lanxiaowei 
 * 
 */  
public class FunctionQueryTest {  
    private static final DateFormat formate = new SimpleDateFormat("yyyy-MM-dd");  
    public static void main(String[] args) throws Exception {  
        String indexDir = "C:/lucenedir-functionquery";  
        Directory directory = FSDirectory.open(Paths.get(indexDir));  
          
        //System.out.println(0.001953125f * 100000000 * 0.001953125f / 100000000);  
        //创建测试索引[注意:只用创建一次,第二次运行前请注释掉这行代码]  
        //createIndex(directory);  
          
          
        IndexReader reader = DirectoryReader.open(directory);  
        IndexSearcher searcher = new IndexSearcher(reader);  
        //创建一个普通的TermQuery  
        TermQuery termQuery = new TermQuery(new Term("title", "solr"));  
        //根据可以计算日期衰减因子的自定义ValueSource来创建FunctionQuery  
        FunctionQuery functionQuery = new FunctionQuery(new DateDampingValueSouce("publishDate"));   
        //自定义评分查询[CustomScoreQuery将普通Query和FunctionQuery组合在一起,至于两者的Query评分按什么算法计算得到最后得分,由用户自己去重写来干预评分]  
        //默认实现是把普通查询评分和FunctionQuery高级查询评分相乘求积得到最终得分,你可以自己重写默认的实现  
        CustomScoreQuery customScoreQuery = new CustomScoreQuery(termQuery, functionQuery);  
        //创建排序器[按评分降序排序]  
        Sort sort = new Sort(new SortField[] {SortField.FIELD_SCORE});  
        TopDocs topDocs = searcher.search(customScoreQuery, null, Integer.MAX_VALUE, sort,true,false);  
        ScoreDoc[] docs = topDocs.scoreDocs;  
          
        for (ScoreDoc scoreDoc : docs) {  
            int docID = scoreDoc.doc;  
            Document document = searcher.doc(docID);  
            String title = document.get("title");  
            String publishDateString = document.get("publishDate");  
            System.out.println(publishDateString);  
            long publishMills = Long.valueOf(publishDateString);  
            Date date = new Date(publishMills);  
            publishDateString = formate.format(date);  
            float score = scoreDoc.score;  
            System.out.println(docID + "  " + title + "                    " +   
                publishDateString + "            " + score);  
        }  
          
        reader.close();  
        directory.close();  
    }  
      
    /** 
     * 创建Document对象 
     * @param title              书名 
     * @param publishDateString  书籍出版日期 
     * @return 
     * @throws ParseException 
     */  
    public static Document createDocument(String title,String publishDateString) throws ParseException {  
        Date publishDate = formate.parse(publishDateString);  
        Document doc = new Document();  
        doc.add(new TextField("title",title,Field.Store.YES));  
        doc.add(new LongField("publishDate", publishDate.getTime(),Store.YES));  
        doc.add(new NumericDocValuesField("publishDate", publishDate.getTime()));  
        return doc;  
    }  
      
    //创建测试索引  
    public static void createIndex(Directory directory) throws ParseException, IOException {  
        Analyzer analyzer = new StandardAnalyzer();  
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);  
        indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);  
        IndexWriter writer = new IndexWriter(directory, indexWriterConfig);  
          
        //创建测试索引  
        Document doc1 = createDocument("Lucene in action 2th edition", "2010-05-05");  
        Document doc2 = createDocument("Lucene Progamming", "2008-07-11");  
        Document doc3 = createDocument("Lucene User Guide", "2014-11-24");  
        Document doc4 = createDocument("Lucene5 Cookbook", "2015-01-09");  
        Document doc5 = createDocument("Apache Lucene API 5.0.0", "2015-02-25");  
        Document doc6 = createDocument("Apache Solr 4 Cookbook", "2013-10-22");  
        Document doc7 = createDocument("Administrating Solr", "2015-01-20");  
        Document doc8 = createDocument("Apache Solr Essentials", "2013-08-16");  
        Document doc9 = createDocument("Apache Solr High Performance", "2014-06-28");  
        Document doc10 = createDocument("Apache Solr API 5.0.0", "2015-03-02");  
          
        writer.addDocument(doc1);  
        writer.addDocument(doc2);  
        writer.addDocument(doc3);  
        writer.addDocument(doc4);  
        writer.addDocument(doc5);  
        writer.addDocument(doc6);  
        writer.addDocument(doc7);  
        writer.addDocument(doc8);  
        writer.addDocument(doc9);  
        writer.addDocument(doc10);  
        writer.close();  
    }  
}  

 运行测试结果如图:

本文转载自:http://iamyida.iteye.com/blog/2201291

共有 人打赏支持
小致dad
粉丝 144
博文 536
码字总数 580295
作品 0
济南
技术主管
私信 提问
打造自己的搜索引擎

这周项目要做一个搜索引擎系统,于是,我看看了看上下左右,看来只有我来弄了~~ 代码其中参考了@红薯的Lucene 早年分享的代码,与一些朋友的精华博客。算是入门了,这个入门花了我40篇日志。...

linapex
2015/11/26
133
0
solr:关于dismax的使用情况

首先说说 dismax这个功能,它是基于lucene的DisjunctionMaxQuery去 扩展的,就是说,实际上用到的就是DisjunctionMaxQuery的这个查询类。查询api,可以知道是 对查询的几个域中,取最大的打分...

momoHuang
2013/07/18
0
3
TngouDB 0.2 beta 发布,中文搜索引擎数据库

TngouDB 中文索引数据库 0.2 beta 版本 主要改进: 1、数据存储引擎Lucene4更新到Lucene5。 2、增加了并发增、删、改的功能。 3、添加了返回状态码 4、重构了回收链接已经关闭链接功能。 需要...

tngou
2015/07/01
2K
13
solr,functionquery实现的是什么功能

我是小白,希望能详细解答,最好举例,函数查询不是查询的函数形式,是对文档进行评分?这是我的看法,但是凭什么分,为什么评分,这里文档是指什么

lyle_5
2012/08/20
699
1
Solr的函数查询(FunctionQuery)

作用 通过函数查询让我们可以利用 numeric域的值或者与域相关的的某个特定的值的函数,来对文档进行评分。 如何使用 这里主要有两种方法可以使用函数查询,这两种方法都是通过solr http 接口...

Zero零_度
2015/08/17
0
1

没有更多内容

加载失败,请刷新页面

加载更多

Java 如何实现线程间通信?

正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了。 本文涉及到的知识点: thread.join(), object....

颖辉小居
25分钟前
1
0
记一次阿里云服务器运行慢排除

公司测试环境用的阿里云服务器+docker部署的,一共跑了14个项目。之前几个月一直OK,最近几天突然很卡很慢。刚开始以为是项目问题,又是扩大内存,又是清减插件,甚至停了一半项目。结果CPU...

李玉长
26分钟前
2
0
统一客服消息返回错误:{"errcode":43004,"errmsg":"require subscribe hint: [9Vv08633952]"}

公众号或者小程序发送客服消息错误: {"errcode":43004,"errmsg":"require subscribe hint: [9Vv08633952]"} 场景:小程序使用公众号的服务消息,推送消息,如果接收人没有关注公众号,就会出...

tianma3798
37分钟前
1
0
Rainbond V5.0 Beta公测公告

Rainbond支撑企业应用的开发、架构、交付和运维的全流程,通过“无侵入”架构无缝衔接各类企业应用,底层资源可以对接和管理IaaS、虚拟机和物理服务器 Rainbond V5.0即日起开启Beta版本公测,...

好雨云帮
55分钟前
2
0
Word Pattern(leetcode290)

Given a pattern and a string str, find if str follows the same pattern. Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empt......

woshixin
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部