`
fhqibjg
  • 浏览: 55101 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

lucene 如何在query完成后进行过滤去重(不引响分页功能)

阅读更多

目的:

    最近项目中有用到lucene,第一次使用些开源工具记录下从中遇到的开发问题。

 

版本:

     3.6

 

需求:

     在创建索引时一个主字段可能对应多条索引记录,其它字段数据不同。如a(1,2,b(3,4))索引就要建成(1,2,3)  (1,2,4)这样,查询时根据b中的条件可能查询出多条同一样的记录,所以要根据a的主字段去重。

 

问题:

      lucene在执行query与filter时,是先执行filter把范围缩小后再执行query。我们去重用的是filter过滤器,先去重后再查询就己丢失了b中的部分数据,导致某些b的条件查询不出a

 

上述执行顺序可见 FilteredQuery类的getFilteredScorer方法:

 

static Scorer getFilteredScorer(final IndexReader indexReader,
			final Similarity similarity, final Weight weight,
			final Weight wrapperWeight, final Filter filter) throws IOException {
		assert filter != null;

		final DocIdSet filterDocIdSet = filter.getDocIdSet(indexReader);
		if (filterDocIdSet == null) {
			// this means the filter does not accept any documents.
			return null;
		}

		final DocIdSetIterator filterIter = filterDocIdSet.iterator();
		if (filterIter == null) {
			// this means the filter does not accept any documents.
			return null;
		}

		// we are gonna advance() this scorer, so we set
		// inorder=true/toplevel=false
		final Scorer scorer = weight.scorer(indexReader, true, false);
		return (scorer == null) ? null : new Scorer(similarity, wrapperWeight) {
			private int scorerDoc = -1, filterDoc = -1;

			// optimization: we are topScorer and collect directly using
			// short-circuited algo
			@Override
			public void score(Collector collector) throws IOException {
				int filterDoc = filterIter.nextDoc();
				int scorerDoc = scorer.advance(filterDoc);
				// the normalization trick already applies the boost of this
				// query,
				// so we can use the wrapped scorer directly:
				collector.setScorer(scorer);
				for (;;) {
					if (scorerDoc == filterDoc) {
						// Check if scorer has exhausted, only before
						// collecting.
						if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
							break;
						}
						// 这里作去重处理
						collector.collect(scorerDoc);
						filterDoc = filterIter.nextDoc();
						scorerDoc = scorer.advance(filterDoc);
					} else if (scorerDoc > filterDoc) {
						filterDoc = filterIter.advance(scorerDoc);
					} else {
						scorerDoc = scorer.advance(filterDoc);
					}
				}
			}

			private int advanceToNextCommonDoc() throws IOException {
				for (;;) {
					if (scorerDoc < filterDoc) {
						scorerDoc = scorer.advance(filterDoc);
					} else if (scorerDoc == filterDoc) {
						return scorerDoc;
					} else {
						filterDoc = filterIter.advance(scorerDoc);
					}
				}
			}

			@Override
			public int nextDoc() throws IOException {
				filterDoc = filterIter.nextDoc();
				return advanceToNextCommonDoc();
			}

			@Override
			public int advance(int target) throws IOException {
				if (target > filterDoc) {
					filterDoc = filterIter.advance(target);
				}
				return advanceToNextCommonDoc();
			}

			@Override
			public int docID() {
				return scorerDoc;
			}

			@Override
			public float score() throws IOException {
				return scorer.score();
			}
		};
	}

 

 

 

 

解决:

      由于从lucene源码中可以看到,查询那里没提供参数来改变filter与query的执行顺序。所以只好改动源码,在最后查询出的结果处自己去做数据的去重过滤。

 

 涉及改动类:

        IndexSearcher.java,  HitQueue.java,  FilteredQuery.java

 

 说明:

       这里HitQueue.java要重写是因为IndexSearcher.java被自定义,可源码中的HitQueue没被定义成public继而引用不了。这里只改动三个类是取巧了,以牺牲 性能为代价下面将会有说明,其实在本需求中要改动的远远不止这三个类。

 

     IndexSearcher改动:

           在查询时无论使用search方法还是searchAfter,最后源码跟踪都是执行以下代码 :

 

 	@Override
					public void search(Weight weight, Filter filter, Collector collector)
							throws IOException {
						// TODO: should we make this
						// threaded...? the Collector could be sync'd?

						// always use single thread:
						for (int i = 0; i < subReaders.length; i++) { // search each subreader
							collector.setNextReader(subReaders[i], docBase + docStarts[i]);
							final Scorer scorer = (filter == null) ? weight.scorer(
									subReaders[i], !collector.acceptsDocsOutOfOrder(), true)
									: FilteredQuery.getFilteredScorer(subReaders[i],
											getSimilarity(), weight, weight, filter);
							if (scorer != null) {
								scorer.score(collector);
							}
							
							//break;
						}
					}
	  

 

 

  所以我们要对此处进行改动,当然这只针对IndexSearcher中executor为空即没传入ExecutorService的             情况下(可自己跟踪源码查看)。先将关键处改动后的代码贴出:

 

	@Override
	public void search(Weight weight, Filter filter, Collector collector)
			throws IOException {
		Map<String, String> idVersionKey = new HashMap<String, String>();
		// TODO: should we make this
		// threaded...? the Collector could be sync'd?

		// always use single thread:
		for (int i = 0; i < subReaders.length; i++) { // search each subreader
			collector.setNextReader(subReaders[i], docBase + docStarts[i]);
			final Scorer scorer = (filter == null) ? weight.scorer(
					subReaders[i], !collector.acceptsDocsOutOfOrder(), true)
					: MyFilteredQuery.getFilteredScorer(subReaders[i],
							getSimilarity(), weight, weight, filter, this,
							idVersionKey);
			if (scorer != null) {
				scorer.score(collector);
			}

			// break;
		}
	}

 

 

 

      FilteredQuery改动:

            上面代码中己能看到FilteredQuery的改动,其方法为getFilteredScorer。可先将此方法复制一份,改动形参(只所以不去除老方法是因为此处还有其本能中的一个类部类在调用)。getFilteredScorer末改动源码在问题部份己贴出,现贴改动后的方法:

 

static Scorer getFilteredScorer(final IndexReader indexReader,
						final Similarity similarity, final Weight weight,
						final Weight wrapperWeight, final Filter filter, final MyIndexSearcher myIndexSearch,
						final Map<String, String> idVersionKey) throws IOException {
					assert filter != null;

					final DocIdSet filterDocIdSet = filter.getDocIdSet(indexReader);
					if (filterDocIdSet == null) {
						// this means the filter does not accept any documents.
						return null;
					}

					final DocIdSetIterator filterIter = filterDocIdSet.iterator();
					if (filterIter == null) {
						// this means the filter does not accept any documents.
						return null;
					}

					// we are gonna advance() this scorer, so we set
					// inorder=true/toplevel=false
					final Scorer scorer = weight.scorer(indexReader, true, false);
					return (scorer == null) ? null : new Scorer(similarity, wrapperWeight) {
						private int scorerDoc = -1, filterDoc = -1;

						// optimization: we are topScorer and collect directly using
						// short-circuited algo
						@Override
						public void score(Collector collector) throws IOException {
							int filterDoc = filterIter.nextDoc();
							int scorerDoc = scorer.advance(filterDoc);
							String key = null;
							// the normalization trick already applies the boost of this
							// query,
							// so we can use the wrapped scorer directly:
							collector.setScorer(scorer);
							
							for (;;) {
								if (scorerDoc == filterDoc) {
									// Check if scorer has exhausted, only before
									// collecting.
									if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS) {
										break;
									}
									// 过滤去重
									Document doc = myIndexSearch.doc(scorerDoc);
									key = doc.get("要去重的索引字段");
									
									if (idVersionKey.get(key) == null) {
										collector.collect(scorerDoc);
										idVersionKey.put(key, "exist");
									}  
									
									filterDoc = filterIter.nextDoc();
									scorerDoc = scorer.advance(filterDoc);
								} else if (scorerDoc > filterDoc) {
									filterDoc = filterIter.advance(scorerDoc);
								} else {
									scorerDoc = scorer.advance(filterDoc);
								}
							}
						}

						private int advanceToNextCommonDoc() throws IOException {
							for (;;) {
								if (scorerDoc < filterDoc) {
									scorerDoc = scorer.advance(filterDoc);
								} else if (scorerDoc == filterDoc) {
									return scorerDoc;
								} else {
									filterDoc = filterIter.advance(scorerDoc);
								}
							}
						}

						@Override
						public int nextDoc() throws IOException {
							filterDoc = filterIter.nextDoc();
							return advanceToNextCommonDoc();
						}

						@Override
						public int advance(int target) throws IOException {
							if (target > filterDoc) {
								filterDoc = filterIter.advance(target);
							}
							return advanceToNextCommonDoc();
						}

						@Override
						public int docID() {
							return scorerDoc;
						}

						@Override
						public float score() throws IOException {
							return scorer.score();
						}
					};
				}

   

 

     从代码

 

final DocIdSet filterDocIdSet = filter.getDocIdSet(indexReader);
	if (filterDocIdSet == null) {
		// this means the filter does not accept any documents.
		return null;
	}

    片段中可看出,些处当过滤器为空时下面代码即不会再走,上面所说的“取巧”就是指这里。我们为了保证代码流程走到改动的代码中,所以要给我们的查询定义一个什么都不做的自定义过滤器。

 

    当然,如果你们需求就是先过滤后查询,这里自定义过滤器就派上用场了,这里可以多自段过滤。

 

new Filter() {
					@Override
					public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
						TermDocs termDocs = reader.termDocs(new Term(LuceneKey.MARK_ID,
								LuceneKey.MARK_VALUE));
						final FixedBitSet bitSet = new FixedBitSet(reader.maxDoc());
						final int[] docs = new int[32];
						final int[] freqs = new int[32];

						try {
							do {
								while (true) {
									final int count = termDocs.read(docs, freqs);
									if (count != 0) {
										for (int i = 0; i < count; i++) {
											bitSet.set(docs[i]);
										}
									} else {
										break;
									}
								}
							} while (termDocs.next());

						} finally {
							termDocs.close();
						}
				
						return bitSet;
					}
			}

 

 

这里顺便也贴出多字段自定义过滤器

 

new Filter() {

			@Override
			public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
				TermDocs termDocs = reader.termDocs(new Term("这里只要是全部索引都有的字段就行,用于全部索引查询"));
				final IndexSearcher indexSearch = new IndexSearcher(reader);
				final FixedBitSet bitSet = new FixedBitSet(reader.maxDoc());
				Map<String, String> idVersionKey = new HashMap<String, String>();
				final int[] docs = new int[32];
				final int[] freqs = new int[32];
				String key = null;

				try {
					do {
						while (true) {
							final int count = termDocs.read(docs, freqs);
							if (count != 0) {
								for (int i = 0; i < count; i++) {
									Document doc = indexSearch.doc(docs[i]);
									key = doc.get("去重字段1") + doc.get("去重字段2");
									bitSet.set(docs[i]);
									if (idVersionKey.get(key) == null) {
										bitSet.set(docs[i]);
										idVersionKey.put(key, "exist");
									}
								}
							} else {
								break;
							}
						}
					} while (termDocs.next());

				} finally {
					try {
						termDocs.close();
					} finally {
						indexSearch.close();
					}
				}
			
				return bitSet;
			}
		}

 

 

 注:

      Map<String, String> idVersionKey要从IndexSearcher类的search方法中传入到FilteredQuery类的getFilteredScorer中,是因为subReaders.length可能大于1,这里可能出现有次循环导致过滤结果不准确。出现这种现像的原因是,当你重新添加索引后而又末调用 writer.forceMerge(1),就会出现多个“虚拟子目录”。

 

for (int i = 0; i < subReaders.length; i++) { // search each subreader
							collector.setNextReader(subReaders[i], docBase + docStarts[i]);
							final Scorer scorer = (filter == null) ? weight.scorer(
									subReaders[i], !collector.acceptsDocsOutOfOrder(), true)
									: MyFilteredQuery.getFilteredScorer(subReaders[i],
											getSimilarity(), weight, weight, filter, this,idVersionKey);
							if (scorer != null) {
								scorer.score(collector);
							}
							
							//break;
		}

 

 

 

 

 

分享到:
评论

相关推荐

    lucene的排序过滤和分页.zip

    在大型数据集的搜索场景中,分页是必不可少的功能,它帮助用户逐步浏览大量的搜索结果。Lucene没有内置的分页机制,但可以通过结合Searcher的`docIds`和`scoreDocs`属性来实现。通常,我们先执行一个查询,获取到`...

    Lucene5学习之Filter过滤器

    这使得搜索性能得到了显著提升,因为过滤操作可以在编译查询时就完成,而不是在匹配文档时进行。例如,我们可能希望只返回特定日期范围内的文档,或者只包含某些关键词的文档,这时Filter就能派上用场。 Filter的...

    lucene去重、分组统计

    用到的工具 jsoup+spring+struct+DButil+mysql+lucene 可以配置采集网站的图片,包含分组统计,相同数据合并功能,主要是给群内成员来个demo,让大家有个学习的demo 小试牛刀、临时写的,莫吐槽 需要用到mysql...

    Lucene5学习之分页查询

    为了方便开发,许多基于Lucene的工具和框架如Solr、Elasticsearch提供了更高级的分页功能。例如,Solr支持`start`和`rows`参数来实现分页,Elasticsearch则有`from`和`size`参数,同时提供了更丰富的分页策略,如`...

    lucene查询工具类和IndexSearcher分页查询示例

    在本文中,我们将深入探讨如何使用Lucene查询工具类和`IndexSearcher`进行分页查询,这在处理大量数据时尤其有用。Lucene是一个强大的全文搜索引擎库,它提供了高效、可扩展的文本检索功能。在Java开发环境中,...

    自己写的lucene分页高亮显示代码

    在大型数据集上进行搜索时,一次性返回所有结果并不实际,因此分页搜索显得尤为重要。在 Lucene 中,可以通过 `TopDocs` 类来实现这一功能。`TopDocs` 包含了查询结果的排序信息和总数,可以用来计算出每一页的数据...

    Lucene.net建立索引,检索分页Demo

    在 .NET 平台上,Lucene.net 提供了与原生 Lucene 相同的强大功能,并且完全兼容 .NET Framework 和 .NET Core。 1. **文本分析(Text Analysis)** 文本分析是 Lucene 的关键部分,用于将输入的字符串转换为可...

    使用Lucene4.7实现搜索功能,分页+高亮

    标题中的“使用Lucene4.7实现搜索功能,分页+高亮”表明我们要讨论的是如何利用Apache Lucene 4.7版本来构建一个具备搜索、分页和高亮显示功能的系统。Lucene是一个高性能、全文本搜索引擎库,它提供了强大的文本...

    lucene查询结果集分页代码

    在lucene搜索分页过程中,可以有两种方式 一种是将搜索结果集直接放到session中,但是假如结果集非常大,同时又存在大并发访问的时候,很可能造成服务器的内存不足,而使服务器宕机 还有一种是每次都重新进行搜索,这样...

    lucene第一步---6.分页

    在IT行业中,Lucene是一个非常重要的全文搜索引擎库,尤其在处理大数据量的文本搜索时,它的性能和灵活性得到了广泛认可。本篇文章将带你迈出使用Lucene的第一步,重点关注如何实现分页检索,这对于构建高效、用户...

    Lucene 使用正则表达式

    ### Lucene 使用正则表达式 #### 知识点概览 1. **Lucene简介** 2. **正则表达式(regex)在...通过以上内容,我们可以看到如何在Lucene中使用正则表达式进行高级搜索,这对于处理大量数据时进行精确查询是非常有用的。

    SSH + Lucene + 分页 + 排序 + 高亮 模拟简单新闻网站搜索引擎--data

    在构建一个简单的新闻网站搜索引擎时,SSH(Spring、Struts2和Hibernate)是一个常见的Java Web开发框架组合,而Lucene是Apache开源组织提供的一款强大的全文搜索引擎库。本项目结合了这些技术,实现了分页、排序和...

    对内存中Lucene查询的集合进行分页

    这篇博客文章“对内存中Lucene查询的集合进行分页”探讨的是如何在处理大量数据时,有效地对Lucene查询结果进行分页显示,以提高用户体验并减轻服务器负担。 首先,理解Lucene的基本工作原理至关重要。Lucene通过...

    lucene4.10

    当用户输入查询字符串时,IKAnalyzer会对其进行分词处理,生成对应的查询关键字,然后Lucene会使用这些关键字在倒排索引中进行查找,返回匹配的文档。通过这种方式,即使面对复杂的中文查询,也能快速找到相关结果。...

    Lucene 搜索方法(模糊搜索)

    在IT领域,搜索引擎技术是不可或缺的一部分,而Apache Lucene是一个高性能、全文本搜索库,它为开发者提供了构建自定义搜索引擎应用程序所需的所有工具。本篇我们将深入探讨如何在Lucene中实现模糊搜索,以及相关的...

    lucene部分常用代码

    本文将对Lucene部分常用代码进行详细解释,包括多字段搜索、多条件搜索、过滤等。 多字段搜索 在Lucene中,我们可以使用MultifieldQueryParser来指定多个搜索字段。MultifieldQueryParser可以将多个字段合并到一个...

    springmvc+mybatis+lucene4文档搜索系统(支持分页)

    在本系统中,我们利用SpringMVC的模型绑定功能,结合Lucene和MyBatis的查询能力,实现了基于关键词的文档搜索结果的分页显示。用户可以通过指定页码和每页条目数量来浏览搜索结果,系统会动态地计算总页数并加载相应...

    Lucene4.X实战类baidu搜索的大型文档海量搜索系统-19.Lucene过滤 共4页.pptx

    7. **QueryWrapperFilter**:将Lucene的Query对象转换为过滤器,允许根据查询条件进行过滤。 以`FieldCacheRangeFilter`为例,我们可以通过以下方式创建一个范围过滤器: ```java FieldCacheRangeFilter&lt;Integer&gt; ...

    lucene 学习实战系列(高亮+分页)

    本文将深入探讨如何在Lucene中实现高亮显示搜索结果和高效的分页功能,帮助开发者更好地理解和运用这个强大的工具。 一、Lucene简介 Lucene的核心功能是提供文本的索引和搜索,其内部实现了高效的倒排索引结构,...

    C#+Lucene.Net开发完成的一个自定义WEB搜索引擎

    在Lucene.Net中,查询语句会被解析成Query对象,然后与索引进行匹配。匹配过程中,可以使用评分函数(如TF-IDF)来评估相关性,按相关性高低返回结果。 **七、Web搜索引擎开发流程** 1. **数据采集**:使用网络...

Global site tag (gtag.js) - Google Analytics