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

Lucene5学习之FunctionQuery功能查询

阅读更多

       我猜,大家最大的疑问就是:不是已经有那么多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使用示例,模拟类似这样的场景:

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

package com.yida.framework.lucene5.function;

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);
			}
		};
	}
	
}

 

package com.yida.framework.lucene5.util.score;

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);
	}
}

     

package com.yida.framework.lucene5.function;

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();
	}
}

   

 

    运行测试结果如图:

 

       demo代码请在最底下的附件里下载如果你需要的话,OK,打完收工!

 

      如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

  • 大小: 311.8 KB
  • 大小: 354.8 KB
  • 大小: 636 KB
  • 大小: 656.1 KB
  • 大小: 157 KB
  • 大小: 307.2 KB
  • 大小: 189 KB
  • 大小: 461.6 KB
1
1
分享到:
评论
2 楼 lxwt909 2015-04-13  
pomie2 写道
附件 压缩包 损坏了

谢谢提醒,已修复
1 楼 pomie2 2015-04-13  
附件 压缩包 损坏了

相关推荐

    Lucene5学习之分页查询

    本文将深入探讨"Lucene5学习之分页查询"这一主题,结合给定的标签"源码"和"工具",我们将讨论如何在Lucene5中实现高效的分页查询,并探讨其背后的源码实现。 首先,理解分页查询的重要性是必要的。在大型数据集的...

    Lucene5学习之Facet(续)

    《Lucene5学习之Facet(续)》 在深入探讨Lucene5的Facet功能之前,我们先来了解一下什么是Faceting。Faceting是搜索引擎提供的一种功能,它允许用户通过分类或属性对搜索结果进行细分,帮助用户更精确地探索和理解...

    Lucene5学习之排序-Sort

    “Lucene5学习之排序-Sort”这个标题表明了我们要探讨的是关于Apache Lucene 5版本中的排序功能。Lucene是一个高性能、全文检索库,它提供了强大的文本搜索能力。在这个主题中,我们将深入理解如何在Lucene 5中对...

    Lucene5学习之SpellCheck拼写纠错

    **标题:“Lucene5学习之SpellCheck拼写纠错”** 在深入探讨Lucene5的SpellCheck功能之前,首先需要理解Lucene是什么。Lucene是一个开源的全文检索库,由Apache软件基金会开发,它提供了高性能、可扩展的文本搜索...

    Lucene5学习之Group分组统计

    "Lucene5学习之Group分组统计" 这个标题指出我们要讨论的是关于Apache Lucene 5版本中的一个特定功能——Grouping。在信息检索领域,Lucene是一个高性能、全文搜索引擎库,而Grouping是它提供的一种功能,允许用户对...

    Lucene5学习之Spatial地理位置搜索

    此外,对于复杂的空间查询,如布尔组合查询、多边形查询等,Lucene5 Spatial也提供了相应的API。例如,你可以组合多个查询条件,或者通过构建多边形来搜索特定形状内的文档。 在实际应用中,Lucene5 Spatial常被...

    Lucene5学习之拼音搜索

    本文将围绕“Lucene5学习之拼音搜索”这一主题,详细介绍其拼音搜索的实现原理和实际应用。 首先,我们需要理解拼音搜索的重要性。在中文环境中,由于汉字的复杂性,用户往往习惯于通过输入词语的拼音来寻找信息。...

    Lucene5学习之自定义Collector

    这篇博客“Lucene5学习之自定义Collector”显然聚焦于如何在Lucene 5版本中通过自定义Collector来优化搜索结果的收集过程。Collector是Lucene搜索框架中的一个重要组件,它负责在搜索过程中收集匹配的文档,并根据...

    Lucene5学习之Highlighte关键字高亮

    《Lucene5学习之Highlighter关键字高亮》 在信息技术领域,搜索引擎的使用已经变得无处不在,而其中的关键技术之一就是如何有效地突出显示搜索结果中的关键字,这就是我们今天要探讨的主题——Lucene5中的...

    Lucene5学习之Suggest关键字提示

    总之,Lucene5的Suggest技术为开发者提供了强大的关键词提示功能,通过理解其工作原理和应用场景,我们可以将其巧妙地融入到各种信息检索系统中,提升用户的搜索体验。通过持续学习和实践,我们可以更好地驾驭这一...

    Lucene5学习之增量索引(Zoie)

    总结起来,Lucene5学习之增量索引(Zoie)涉及到的关键技术点包括: 1. 基于Lucene的增量索引解决方案:Zoie系统。 2. 主从复制架构:Index Provider和Index User的角色。 3. 数据变更追踪:通过变更日志实现增量索引...

    Lucene5学习之自定义排序

    本文将深入探讨“Lucene5学习之自定义排序”这一主题,帮助你理解如何在Lucene5中实现自定义的排序规则。 首先,Lucene的核心功能之一就是提供高效的全文检索能力,但默认的搜索结果排序通常是基于相关度得分...

    Lucene5学习之创建索引入门示例

    **Lucene5学习之创建索引入门示例** 在IT领域,搜索引擎的开发与优化是一项关键技术,而Apache Lucene作为一款高性能、全文本搜索库,是许多开发者进行文本检索的首选工具。本文将深入探讨如何使用Lucene5来创建一...

    lucene学习lucene学习

    Lucene 提供了高效、可扩展的索引和搜索功能,允许开发者轻松地在应用程序中集成高级文本检索功能。 在了解 Lucene 的工作原理之前,我们要明确一点:Lucene 不是一个完整的搜索引擎应用,而是用于构建搜索引擎的...

    Lucene5学习之TermVector项向量

    《Lucene5学习之TermVector项向量》 在深入理解Lucene5的搜索引擎功能时,TermVector(项向量)是一个关键的概念,它对于文本分析、信息检索和相关性计算等方面起着至关重要的作用。TermVector是Lucene提供的一种...

    Lucene5学习之多线程创建索引

    《Lucene5学习之多线程创建索引》 在深入了解Lucene5的多线程索引创建之前,我们先来了解一下Lucene的基本概念。Lucene是一个高性能、全文本搜索库,由Apache软件基金会开发。它提供了强大的文本分析、索引和搜索...

    Lucene5学习之Filter过滤器

    《深入理解Lucene5:Filter过滤器的奥秘》 在全文搜索引擎的开发过程中,Lucene作为一款强大的开源搜索引擎库,扮演着至关重要的角色。它提供了丰富的功能,使得开发者能够快速构建高效的搜索系统。其中,Filter...

    Lucene5学习之自定义同义词分词器简单示例

    本篇将聚焦于"Lucene5学习之自定义同义词分词器简单示例",通过这个主题,我们将深入探讨如何在Lucene5中自定义分词器,特别是实现同义词扩展,以提升搜索质量和用户体验。 首先,理解分词器(Analyzer)在Lucene中...

    Lucene5学习之评分Scoring

    《Lucene5学习之评分Scoring》 在信息检索领域,Lucene是一个广泛使用的全文搜索引擎库,尤其在Java开发中应用颇广。在Lucene 5版本中,对于搜索结果的排序和评分机制进行了优化,使得搜索体验更加精准。本文将深入...

Global site tag (gtag.js) - Google Analytics