Lucene 源码分析之倒排索引(二)
本文以及后面几篇文章将讲解如何定位 Lucene 中的倒排索引。内容很多,唯有静下心才能跟着思路遨游。
我们可以思考一下,哪个步骤与倒排索引有关,很容易想到检索文档一定是要查询倒排列表的,那么就从此处入手。检索文档通过调用 IndexSearcher.search(Query query, int n) 方法返回匹配的文档。
public class IndexSearcher {
public TopDocs search(Query query, int n) throws IOException {
return searchAfter(null, query, n);
}
public TopDocs searchAfter(ScoreDoc after, Query query, int numHits) throws IOException {
// ...
return search(query, manager);
}
public <C extends Collector, T> T search(Query query, CollectorManager<C, T> collectorManager) throws IOException {
if (executor == null) {
final C collector = collectorManager.newCollector();
search(query, collector);
return collectorManager.reduce(Collections.singletonList(collector));
}
// ...
}
}
上面是 search 的调用链,最终调用的核心方法是 reduce(...),也就是说 reduce(...) 会返回匹配的文档。
下文通过聚焦 reduce(...) 方法定位 Lucene 中的倒排索引。
reduce(...) 方法的形参是 Collections.singletonList(collector),collector 是由 CollectorManager.newCollector() 方法创建的,而 CollectorManager 创建于上面代码中第二个方法 searchAfter 方法中的匿名内部类,代码如下。
public class IndexSearcher {
public TopDocs searchAfter(ScoreDoc after, Query query, int numHits) throws IOException {
// ...
final CollectorManager<TopScoreDocCollector, TopDocs> manager = new CollectorManager<TopScoreDocCollector, TopDocs>() {
@Override
public TopScoreDocCollector newCollector() throws IOException {
return TopScoreDocCollector.create(cappedNumHits, after);
}
// ...
};
// ...
}
}
public abstract class TopScoreDocCollector extends TopDocsCollector<ScoreDoc> {
public static TopScoreDocCollector create(int numHits, ScoreDoc after) {
return new SimpleTopScoreDocCollector(numHits);
}
}
也就是说 reduce 的形参是一个集合,该集合包含一个 SimpleTopScoreDocCollector 对象。
回到 reduce 的内部实现,调用方也是 searchAfter 方法中的匿名内部类 CollectorManager,代码如下。
public class IndexSearcher {
public TopDocs searchAfter(ScoreDoc after, Query query, int numHits) throws IOException {
// ...
final CollectorManager<TopScoreDocCollector, TopDocs> manager = new CollectorManager<TopScoreDocCollector, TopDocs>() {
// ...
@Override
public TopDocs reduce(Collection<TopScoreDocCollector> collectors) throws IOException {
final TopDocs[] topDocs = new TopDocs[collectors.size()];
int i = 0;
for (TopScoreDocCollector collector : collectors) {
topDocs[i++] = collector.topDocs();
}
return TopDocs.merge(0, cappedNumHits, topDocs, true);
}
};
// ...
}
}
由于 reduce(...) 方法的形参仅有一个元素,reduce(...) 方法退化成执行 SimpleTopScoreDocCollector.topDocs(),其结果就是匹配的文档。
public abstract class TopScoreDocCollector extends TopDocsCollector<ScoreDoc> {
private static class SimpleTopScoreDocCollector extends TopScoreDocCollector {
// ...
}
}
public abstract class TopDocsCollector<T extends ScoreDoc> implements Collector {
public TopDocs topDocs() {
return topDocs(0, topDocsSize());
}
public TopDocs topDocs(int start, int howMany) {
// ...
ScoreDoc[] results = new ScoreDoc[howMany];
// ...
populateResults(results, howMany);
return newTopDocs(results, start);
}
protected void populateResults(ScoreDoc[] results, int howMany) {
for (int i = howMany - 1; i >= 0; i--) {
results[i] = pq.pop();
}
}
}
SimpleTopScoreDocCollector 继承自 TopScoreDocCollector 继承自 TopDocsCollector,实际执行 TopDocsCollector.topDocs()。
时刻记住 reduce() 返回匹配的文档,也就是说 TopDocsCollector. topDocs() 返回匹配的文档。 results 作为 NewTopDocs 的成员变量一定包含了匹配的文档,results 又来自于 pq.pop(),那么 pq 一定包含了匹配的文档。
下面通过聚焦 SimpleTopScoreDocCollector 对象的 pq 定位倒排索引。
回顾 CollectorManager.reduce(...) 所在的 search(...) 方法,在初始化 SimpleTopScoreDocCollector 和 reduce(...) 之间唯一的方法就是另一个 search(…) 方法,一定是在这个方法中赋值了 pq,代码如下。
public class IndexSearcher {
public void search(Query query, Collector results) throws IOException {
search(leafContexts, createNormalizedWeight(query, results.needsScores()), results);
}
protected void search(List<LeafReaderContext> leaves, Weight weight, Collector collector) throws IOException {
for (LeafReaderContext ctx : leaves) { // search each subreader
final LeafCollector leafCollector = collector.getLeafCollector(ctx);
BulkScorer scorer = weight.bulkScorer(ctx);
scorer.score(leafCollector, ctx.reader().getLiveDocs());
}
}
}
一共就三个方法,究竟是在哪个方法中赋值了 pq 呢?一个个分析。
第一个方法,collector.getLeafCollector(ctx) 实际调用的就是 SimpleTopScoreDocCollector.getLeafCollector(ctx)。
public abstract class TopScoreDocCollector extends TopDocsCollector<ScoreDoc> {
private static class SimpleTopScoreDocCollector extends TopScoreDocCollector {
@Override
public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
final int docBase = context.docBase;
return new ScorerLeafCollector() {
@Override
public void collect(int doc) throws IOException {
float score = scorer.score();
totalHits++;
pqTop.doc = doc + docBase;
pqTop.score = score;
pqTop = pq.updateTop();
}
};
}
}
}
可以看到 getLeafCollector(...) 方法返回的 ScorerLeafCollector 类提供了 collect(doc) 方法对 pq 进行操作。也就是说找到调用 collect(doc) 方法的地方也就找到了倒排索引。
下面通过聚焦找到调用 collect() 方法的来源来定位倒排索引。
第二个方法,weight.bulkScorer(ctx) 创建 BulkScorer,而 weight 由 createNormalizedWeight(…) 创建。
public class IndexSearcher {
public Weight createNormalizedWeight(Query query, boolean needsScores) throws IOException {
// ...
return createWeight(query, needsScores, 1f);
}
public Weight createWeight(Query query, boolean needsScores, float boost) throws IOException {
// ...
Weight weight = query.createWeight(this, needsScores, boost);
// ...
return weight;
}
}
假设 query 是最简单的 TermQuery,createWeight(…) 代码如下。
public class TermQuery extends Query {
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
// ...
return new TermWeight(searcher, needsScores, boost, termState);
}
}
最终返回的是 TermWeight 对象,那么 weight.bulkScorer(ctx) 实现类代码如下。
public abstract class Weight implements SegmentCacheable {
public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
// ...
return new DefaultBulkScorer(scorer);
}
}
最终返回的是一个 DefaultBulkScorer 对象。
第三个方法,scorer.score(…),实际调用类是 DefaultBulkScorer,代码如下。
public abstract class Weight implements SegmentCacheable {
protected static class DefaultBulkScorer extends BulkScorer {
// ...
}
}
public abstract class BulkScorer {
public void score(LeafCollector collector, Bits acceptDocs) throws IOException {
final int next = score(collector, acceptDocs, 0, DocIdSetIterator.NO_MORE_DOCS);
}
}
BulkScorer.score(…) 内部调用的还是 DefaultBulkScorer 中重构的 score(…) 方法,代码如下。
public abstract class Weight implements SegmentCacheable {
protected static class DefaultBulkScorer extends BulkScorer {
@Override
public int score(LeafCollector collector, Bits acceptDocs, int min, int max) throws IOException {
collector.setScorer(scorer);
if (scorer.docID() == -1 && min == 0 && max == DocIdSetIterator.NO_MORE_DOCS) {
scoreAll(collector, iterator, twoPhase, acceptDocs);
return DocIdSetIterator.NO_MORE_DOCS;
}
}
static void scoreAll(LeafCollector collector, DocIdSetIterator iterator, TwoPhaseIterator twoPhase, Bits acceptDocs) throws IOException {
if (twoPhase == null) {
for (int doc = iterator.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = iterator.nextDoc()) {
if (acceptDocs == null || acceptDocs.get(doc)) {
collector.collect(doc);
}
}
}
}
}
}
看到了什么!找到了调用 collect(…) 方法的代码。
Lucene 源码分析之倒排索引(二)的更多相关文章
- Lucene 源码分析之倒排索引(三)
上文找到了 collect(-) 方法,其形参就是匹配的文档 Id,根据代码上下文,其中 doc 是由 iterator.nextDoc() 获得的,那 DefaultBulkScorer.itera ...
- Lucene 源码分析之倒排索引(一)
倒排索引是 Lucene 的核心数据结构,该系列文章将从源码层面(源码版本:Lucene-7.3.0)分析.该系列文章将以如下的思路展开. 什么是倒排索引? 如何定位 Lucene 中的倒排索引? 倒 ...
- 手机自动化测试:appium源码分析之bootstrap二
手机自动化测试:appium源码分析之bootstrap二 在bootstrap项目中的io.appium.android.bootstrap.handler包中的类都是对应的指令类, priva ...
- 一个lucene源码分析的博客
ITpub上的一个lucene源码分析的博客,写的比较全面:http://blog.itpub.net/28624388/cid-93356-list-1/
- lucene源码分析的一些资料
针对lucene6.1较新的分析:http://46aae4d1e2371e4aa769798941cef698.devproxy.yunshipei.com/conansonic/article/d ...
- spark 源码分析之十二 -- Spark内置RPC机制剖析之八Spark RPC总结
在spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv中,剖析了NettyRpcEnv的创建过程. Dispatcher.NettyStreamManager.T ...
- Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析
什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...
- Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果
一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...
- ABP源码分析三十二:ABP.SignalR
Realtime Realtime是ABP底层模块提供的功能,用于管理在线用户.它是使用SignalR实现给在线用户发送通知的功能的前提 IOnlineClient/OnlineClient: 封装在 ...
随机推荐
- oracle角色、权限和用户
oracle角色.权限和用户 [转贴 2010-1-25 10:29:45] 字号:大 中 小 Oracle内置角色connect与resource的权限 首先用一个命令赋予user用户con ...
- JDBC基本使用
J2EE技术规范(二)——JDBC 分类: java2012-12-03 14:25 1060人阅读 评论(8) 收藏 举报 一.了解JDBC (1) JDBC是以统一方式访问数据库的API (2) ...
- Ubuntu系统下配置IP地址方法介绍
配置IP方式有两种: 1.通过命令直接配置 sudo ifconfig eth0 IP地址 netmask 子网掩码------配置IP地 sudo route add default gw 网关-- ...
- 【读书笔记】C++Primer---第二章
1.C++基本数据类型:字符型.整型.浮点型等: 2.算术类型如下表,下表只是表示C++要求的最小储存空间,但是一般编译器使用的都比所要求的储存空间大,以前类型所能表示的最大值也因机器的不同而有不同的 ...
- Ocelot中文文档-跟踪
Ocelot使用一个杰出的项目Butterfly 提供了跟踪功能. 为了使用跟踪,请阅读Butterfly的文档. 在Ocelot中如果你想跟踪一个ReRoute,你需要做如下事情: 在Configu ...
- require/exports 和 import/export 区别
零.区别 1.require/exports 是 CommonJS 的标准,适用范围如 Node.js 2.import/export 是 ES6 的标准,适用范围如 React 一.间接获取对象 ( ...
- java运行机制、Jdk版本及Java环境变量
一.语言特性 计算机高级语言按程序的执行方式可分为:编译型和解释型两种.编译型的语言是指使用专门的编译器,针对特定的平台(操作系统)一次性翻译成被该平台硬件执行的机器码,并包装成该平台可执行性程序文件 ...
- ML.NET指南
ML.NET是一个免费的.开源和跨平台的机器学习框架,使您能够构建定制的机器学习解决方案,并将它们集成到您的. net应用程序.本指南提供了许多关于与ML.NET合作资源. 关于ML.NET的更多信息 ...
- Linux(二十二)Ubuntu安装和配置
Ubuntu的介绍 Ubuntu是一个以桌面应用为主的开源GNU/Linux操作系统,Ubuntu是基于GNU/Linux,支持x86.amd64(即x64)和ppc架构,由全球化的专业开发团队(Ca ...
- Selenium2Lib库之操作浏览器相关的关键字实战
1.1 操作浏览器相关的关键字 Selenium2Lib提供了与浏览器交互的关键词 1.1.1 Open Browser关键字 按F5 查看Open Browser关键字的说明,如下图: Open ...