一步一步跟我学习lucene(13)---lucene搜索之自定义排序的实现原理和编写自己的自定义排序工具

自定义排序说明

我们在做lucene搜索的时候,可能会需要排序功能,虽然lucene内置了多种类型的排序,但是如果在需要先进行某些值的运算然后在排序的时候就有点显得无能为力了;

要做自定义查询,我们就要研究lucene已经实现的排序功能,lucene的所有排序都是要继承FieldComparator,然后重写内部实现,这里以IntComparator为例子来查看其实现;

IntComparator相关实现

其类的声明为 public static class IntComparator extends NumericComparator<Integer>,这里说明IntComparator接收的是Integer类型的参数,即只处理IntField的排序;

IntComparator声明的参数为:

private final int[] values;
    private int bottom;                           // Value of bottom of queue
    private int topValue;

查看copy方法可知

  • values随着类初始化而初始化其长度
  • values用于存储NumericDocValues中读取到的内容

具体实现如下:

values的初始化

/**
     * Creates a new comparator based on {@link Integer#compare} for {@code numHits}.
     * When a document has no value for the field, {@code missingValue} is substituted.
     */
    public IntComparator(int numHits, String field, Integer missingValue) {
      super(field, missingValue);
      values = new int[numHits];
    }

values值填充(此为IntComparator的处理方式)

 @Override
    public void copy(int slot, int doc) {
      int v2 = (int) currentReaderValues.get(doc);
      // Test for v2 == 0 to save Bits.get method call for
      // the common case (doc has value and value is non-zero):
      if (docsWithField != null && v2 == 0 && !docsWithField.get(doc)) {
        v2 = missingValue;
      }

      values[slot] = v2;
    }

这些实现都是类似的,我们的应用实现自定义排序的时候需要做的是对binaryDocValues或NumericDocValues的值进行计算,然后实现FieldComparator内部方法,对应IntComparator就是如上的值copy操作;

然后我们需要实现compareTop、compareBottom和compare,IntComparator的实现为:

    @Override
    public int compare(int slot1, int slot2) {
      return Integer.compare(values[slot1], values[slot2]);
    }

    @Override
    public int compareBottom(int doc) {
      int v2 = (int) currentReaderValues.get(doc);
      // Test for v2 == 0 to save Bits.get method call for
      // the common case (doc has value and value is non-zero):
      if (docsWithField != null && v2 == 0 && !docsWithField.get(doc)) {
        v2 = missingValue;
      }

      return Integer.compare(bottom, v2);
    }
    @Override
    public int compareTop(int doc) {
      int docValue = (int) currentReaderValues.get(doc);
      // Test for docValue == 0 to save Bits.get method call for
      // the common case (doc has value and value is non-zero):
      if (docsWithField != null && docValue == 0 && !docsWithField.get(doc)) {
        docValue = missingValue;
      }
      return Integer.compare(topValue, docValue);
    }

实现自己的FieldComparator

要实现FieldComparator,需要对接收参数进行处理,定义处理值的集合,同时定义BinaryDocValues和接收的参数等,这里我写了一个通用的比较器,代码如下:

package com.lucene.search;

import java.io.IOException;

import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.SimpleFieldComparator;

import com.lucene.util.ObjectUtil;

/**自定义comparator
 * @author lenovo
 *
 */
public class SelfDefineComparator extends SimpleFieldComparator<String> {
	private Object[] values;//定义的Object[],同IntComparator
	private Object bottom;
	private Object top;
	private String field;
	private BinaryDocValues binaryDocValues;//接收的BinaryDocValues,同IntComparator中的NumericDocValues
	private ObjectUtil objectUtil;//这里为了便于拓展用接口代替抽象类
	private Object[] params;//接收的参数

	public SelfDefineComparator(String field, int numHits, Object[] params,ObjectUtil objectUtil) {
		values = new Object[numHits];
		this.objectUtil = objectUtil;
		this.field = field;
		this.params = params;
	}  

	@Override
	public void setBottom(int slot) {
		this.bottom = values[slot];
	}

	@Override
	public int compareBottom(int doc) throws IOException {
		Object distance = getValues(doc);
		return (bottom.toString()).compareTo(distance.toString());
	}

	@Override
	public int compareTop(int doc) throws IOException {
		Object distance = getValues(doc);
		return objectUtil.compareTo(top,distance);
	}

	@Override
	public void copy(int slot, int doc) throws IOException {
		values[slot] = getValues(doc);
	}

	/**???docID?????value
	 * @param doc
	 * @return
	 */
	private Object getValues(int doc) {
		Object instance = objectUtil.getValues(doc,params,binaryDocValues) ;
		return instance;
	}

	@Override
	protected void doSetNextReader(LeafReaderContext context)
			throws IOException {
		binaryDocValues = DocValues.getBinary(context.reader(), field);//context.reader().getBinaryDocValues(field);
	}

	@Override
	public int compare(int slot1, int slot2) {
		return objectUtil.compareTo(values[slot1],values[slot2]);
	}
	@Override
	public void setTopValue(String value) {
		this.top = value;
	}

	@Override
	public String value(int slot) {
		return values[slot].toString();
	}

}

其中ObjectUtil是一个接口,定义了值处理的过程,最终是要服务于comparator的compare方法的,同时对comparator的内部compare方法进行了定义

ObjectUtil接口定义如下:

package com.lucene.util;

import org.apache.lucene.index.BinaryDocValues;

public interface ObjectUtil {

	/**自定义的获取处理值的方法
	 * @param doc
	 * @param params
	 * @param binaryDocValues
	 * @return
	 */
	public abstract Object getValues(int doc, Object[] params, BinaryDocValues binaryDocValues) ;

	/**compare比较器实现
	 * @param object
	 * @param object2
	 * @return
	 */
	public abstract int compareTo(Object object, Object object2);

}

我们不仅要提供比较器和comparator,同时还要提供接收用户输入的FiledComparatorSource

package com.lucene.search;

import java.io.IOException;

import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.FieldComparatorSource;

import com.lucene.util.ObjectUtil;

/**comparator用于接收用户原始输入,继承自FieldComparatorSource实现了自定义comparator的构建
 * @author lenovo
 *
 */
public class SelfDefineComparatorSource extends FieldComparatorSource {
	private Object[] params;//接收的参数
	private ObjectUtil objectUtil;//这里为了便于拓展用接口代替抽象类

	public Object[] getParams() {
		return params;
	}

	public void setParams(Object[] params) {
		this.params = params;
	}

	public ObjectUtil getObjectUtil() {
		return objectUtil;
	}

	public void setObjectUtil(ObjectUtil objectUtil) {
		this.objectUtil = objectUtil;
	}

	public SelfDefineComparatorSource(Object[] params, ObjectUtil objectUtil) {
		super();
		this.params = params;
		this.objectUtil = objectUtil;
	}

	@Override
	public FieldComparator<?> newComparator(String fieldname, int numHits,
			int sortPos, boolean reversed) throws IOException {
		//实际比较由SelfDefineComparator实现
		return new SelfDefineComparator(fieldname, numHits, params, objectUtil);
	}
}

相关测试程序,这里我们模拟一个StringComparator,对String值进行排序

package com.lucene.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
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.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
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.search.TopFieldDocs;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef;

import com.lucene.util.CustomerUtil;
import com.lucene.util.ObjectUtil;
import com.lucene.util.StringComparaUtil;

/**
 *
 * @author 吴莹桂
 *
 */
public class SortTest {
	public static void main(String[] args) throws Exception {
		RAMDirectory directory = new RAMDirectory();
		Analyzer analyzer = new StandardAnalyzer();
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
		indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
		IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
        addDocument(indexWriter, "B");
        addDocument(indexWriter, "D");
        addDocument(indexWriter, "A");
        addDocument(indexWriter, "E");
        indexWriter.commit();
        indexWriter.close();
        IndexReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);
        Query query = new MatchAllDocsQuery();
        ObjectUtil util = new StringComparaUtil();
        Sort sort = new Sort(new SortField("name",new SelfDefineComparatorSource(new Object[]{},util),true));
        TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE, sort);
        ScoreDoc[] docs = topDocs.scoreDocs;
        for(ScoreDoc doc : docs){
            Document document = searcher.doc(doc.doc);
            System.out.println(document.get("name"));
        }
	}

	private static void addDocument(IndexWriter writer,String name) throws Exception{
        Document document = new Document();
        document.add(new StringField("name",name,Field.Store.YES));
        document.add(new BinaryDocValuesField("name", new BytesRef(name.getBytes())));
        writer.addDocument(document);
    }
}

其对应的ObjectUtil实现如下:

package com.lucene.util;

import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.util.BytesRef;

public class StringComparaUtil implements ObjectUtil {

	@Override
	public Object getValues(int doc, Object[] params,
			BinaryDocValues binaryDocValues) {
		BytesRef bytesRef = binaryDocValues.get(doc);
		String value = bytesRef.utf8ToString();
		return value;
	}

	@Override
	public int compareTo(Object object, Object object2) {
		// TODO Auto-generated method stub
		return object.toString().compareTo(object2.toString());
	}

}

时间不早了,今天先写到这里,明天奉上相关源码下载

一步一步跟我学习lucene是对近期做lucene索引的总结,大家有问题的话联系本人的Q-Q:  891922381,同时本人新建Q-Q群:106570134(lucene,solr,netty,hadoop),如蒙加入,不胜感激,大家共同探讨,本人争取每日一博,希望大家持续关注,会带给大家惊喜的



时间: 2024-12-27 08:16:53

一步一步跟我学习lucene(13)---lucene搜索之自定义排序的实现原理和编写自己的自定义排序工具的相关文章

[原创]java WEB学习笔记13:JSP介绍(背景,特点,原理)

JSP介绍:(理解) 1)JSP背景 ①在很多动态网页中,绝大部分内容都是固定不变的,只有局部内容需要动态产生和改变: ②如果使用Servlet程序来输出只有局部内容需要动态改变的网页,其中所有的静态内容也需要程序员用Java程序代码产生,整个Servlet程序的代码将非常臃肿,编写和维护都将非常困难: ③对大量静态内容的美工设计和相关HTML语句的编写,并不是程序员所要做的工作,程序员对此也不一定在行.网页美工设计和制作人员不懂Java编程,更是无法来完成这样的工作: ④为了弥补 Servle

一步一步跟我学习lucene(19)---lucene增量更新和NRT(near-real-time)Query近实时查询

这两天加班,不能兼顾博客的更新,请大家见谅. 有时候我们创建完索引之后,数据源可能有更新的内容,而我们又想像数据库那样能直接体现在查询中,这里就是我们所说的增量索引.对于这样的需求我们怎么来实现呢?lucene内部是没有提供这种增量索引的实现的: 这里我们一般可能会想到,将之前的索引全部删除,然后进行索引的重建.对于这种做法,如果数据源的条数不是特别大的情况下倒还可以,如果数据源的条数特别大的话,势必会造成查询数据耗时,同时索引的构建也是比较耗时的,几相叠加,势必可能造成查询的时候数据缺失的情况

一步一步跟我学习lucene(9)---lucene搜索之拼写检查和相似度查询提示(spellcheck)

suggest应用场景 用户的输入行为是不确定的,而我们在写程序的时候总是想让用户按照指定的内容或指定格式的内容进行搜索,这里就要进行人工干预用户输入的搜索条件了:我们在用百度谷歌等搜索引擎的时候经常会看到按键放下的时候直接会提示用户是否想搜索某些相关的内容,恰好lucene在开发的时候想到了这一点,lucene提供的suggest包正是用来解决上述问题的. suggest包联想词相关介绍 suggest包提供了lucene的自动补全或者拼写检查的支持: 拼写检查相关的类在org.apache.

一步一步跟我学习lucene(16)---lucene搜索之facet查询查询示例(2)

本篇是接一步一步跟我学习lucene(14)---lucene搜索之facet索引原理和facet查询实例(http://blog.csdn.net/wuyinggui10000/article/details/45973769),上篇主要是统计facet的dim和每个种类对应的数量,个人感觉这个跟lucene的group不同的在于facet的存储类似于hash(key-field-value)形式的,而group则是单一的map(key-value)形式的,虽然都可以统计某一品类的数量,显然f

一步一步跟我学习lucene(14)---lucene搜索之facet查询原理和facet查询实例

Facet说明 我们在浏览网站的时候,经常会遇到按某一类条件查询的情况,这种情况尤以电商网站最多,以天猫商城为例,我们选择某一个品牌,系统会将该品牌对应的商品展示出来,效果图如下: 如上图,我们关注的是品牌,选购热点等方面,对于类似的功能我们用lucene的term查询当然可以,但是在数据量特别大的情况下还用普通查询来实现显然会因为FSDirectory.open等耗时的操作造成查询效率的低下,同时普通查询是全部document都扫描一遍,这样显然造成了查询效率低: lucene提供了facet

一步一步跟我学习lucene(12)---lucene搜索之分组处理group查询

grouping介绍 我们在做lucene搜索的时候,可能会用到对某个条件的数据进行统计,比如统计有多少个省份,在sql查询中我们可以用distinct来完成类似的功能,也可以用group by来对查询的列进行分组查询.在lucene中我们实现类似的功能怎么做呢,比较费时的做法时我们查询出所有的结果,然后对结果里边的省份对应的field查询出来,往set里边放,显然这种做法效率低,不可取:lucene为了解决上述问题,提供了用于分组操作的模块group,group主要用户处理不同lucene中含

一步一步跟我学习lucene(11)---lucene搜索之高亮显示highlighter

highlighter介绍 这几天一直加班,博客有三天没有更新了,望见谅:我们在做查询的时候,希望对我们自己的搜索结果与搜索内容相近的地方进行着重显示,就如下面的效果 这里我们搜索的内容是"一步一步跟我学习lucene",搜索引擎展示的结果中对用户的输入信息进行了配色方面的处理,这种区分正常文本和输入内容的效果即是高亮显示: 这样做的好处: 视觉上让人便于查找有搜索对应的文本块: 界面展示更友好: lucene提供了highlighter插件来体现类似的效果: highlighter对

一步一步跟我学习lucene(10)---lucene搜索之联想词提示之suggest原理和应用

昨天了解了suggest包中的spell相关的内容,主要是拼写检查和相似度查询提示: 今天准备了解下关于联想词的内容,lucene的联想词是在org.apache.lucene.search.suggest包下边,提供了自动补全或者联想提示功能的支持: InputIterator说明 InputIterator是一个支持枚举term,weight,payload三元组的供suggester使用的接口,目前仅支持AnalyzingSuggester,FuzzySuggester andAnalyz

一步一步跟我学习lucene(7)---lucene搜索之IndexSearcher构建过程

最近一直在写一步一步跟我学习lucene系列(http://blog.csdn.net/wuyinggui10000/article/category/3173543),个人的博客也收到了很多的访问量,谢谢大家的关注,这也是对我个人的一个激励,O(∩_∩)O哈哈~,个人感觉在博客的编写过程中自己收获了很多,我会一直继续下去,在工作的过程中自己也会写出更多类似系列的博客,也算是对自己只是的一种积累: IndexSearcher 搜索引擎的构建分为索引内容和查询索引两个大方面,这里要介绍的是luce