searcher.Search(types.SearchRequest{Text: "百度中国"})
// 查找满足搜索条件的文档,此函数线程安全
func (engine *Engine) Search(request types.SearchRequest) (output types.SearchResponse) {
if !engine.initialized {
log.Fatal("必须先初始化引擎")
} var rankOptions types.RankOptions
if request.RankOptions == nil {
rankOptions = *engine.initOptions.DefaultRankOptions
} else {
rankOptions = *request.RankOptions
}
if rankOptions.ScoringCriteria == nil {
rankOptions.ScoringCriteria = engine.initOptions.DefaultRankOptions.ScoringCriteria
} // 收集关键词
tokens := []string{}
if request.Text != "" {
querySegments := engine.segmenter.Segment([]byte(request.Text))
for _, s := range querySegments {
token := s.Token().Text()
if !engine.stopTokens.IsStopToken(token) {
tokens = append(tokens, s.Token().Text())
}
}
} else {
for _, t := range request.Tokens {
tokens = append(tokens, t)
}
} // 建立排序器返回的通信通道
rankerReturnChannel := make(
chan rankerReturnRequest, engine.initOptions.NumShards) // 生成查找请求
lookupRequest := indexerLookupRequest{
countDocsOnly: request.CountDocsOnly,
tokens: tokens,
labels: request.Labels,
docIds: request.DocIds,
options: rankOptions,
rankerReturnChannel: rankerReturnChannel,
orderless: request.Orderless,
} // 向索引器发送查找请求
for shard := ; shard < engine.initOptions.NumShards; shard++ {
engine.indexerLookupChannels[shard] <- lookupRequest
} // 从通信通道读取排序器的输出
numDocs :=
rankOutput := types.ScoredDocuments{}
timeout := request.Timeout
isTimeout := false
if timeout <= {
// 不设置超时
for shard := ; shard < engine.initOptions.NumShards; shard++ {
rankerOutput := <-rankerReturnChannel
if !request.CountDocsOnly {
for _, doc := range rankerOutput.docs {
rankOutput = append(rankOutput, doc)
}
}
numDocs += rankerOutput.numDocs
}
} else {
// 设置超时
deadline := time.Now().Add(time.Millisecond * time.Duration(request.Timeout))
for shard := ; shard < engine.initOptions.NumShards; shard++ {
select {
case rankerOutput := <-rankerReturnChannel:
if !request.CountDocsOnly {
for _, doc := range rankerOutput.docs {
rankOutput = append(rankOutput, doc)
}
}
numDocs += rankerOutput.numDocs
case <-time.After(deadline.Sub(time.Now())):
isTimeout = true
break
}
}
} // 再排序
if !request.CountDocsOnly && !request.Orderless {
if rankOptions.ReverseOrder {
sort.Sort(sort.Reverse(rankOutput))
} else {
sort.Sort(rankOutput)
}
} // 准备输出
output.Tokens = tokens
// 仅当CountDocsOnly为false时才充填output.Docs
if !request.CountDocsOnly {
if request.Orderless {
// 无序状态无需对Offset截断
output.Docs = rankOutput
} else {
var start, end int
if rankOptions.MaxOutputs == {
start = utils.MinInt(rankOptions.OutputOffset, len(rankOutput))
end = len(rankOutput)
} else {
start = utils.MinInt(rankOptions.OutputOffset, len(rankOutput))
end = utils.MinInt(start+rankOptions.MaxOutputs, len(rankOutput))
}
output.Docs = rankOutput[start:end]
}
}
output.NumDocs = numDocs
output.Timeout = isTimeout
return
}
索引器接受查找请求:
func (engine *Engine) indexerLookupWorker(shard int) {
for {
request := <-engine.indexerLookupChannels[shard] // 关键 var docs []types.IndexedDocument
var numDocs int
if request.docIds == nil {
docs, numDocs = engine.indexers[shard].Lookup(request.tokens, request.labels, nil, request.countDocsOnly)
} else {
docs, numDocs = engine.indexers[shard].Lookup(request.tokens, request.labels, request.docIds, request.countDocsOnly)
} if request.countDocsOnly {
request.rankerReturnChannel <- rankerReturnRequest{numDocs: numDocs}
continue
} if len(docs) == {
request.rankerReturnChannel <- rankerReturnRequest{}
continue
} if request.orderless {
var outputDocs []types.ScoredDocument
for _, d := range docs {
outputDocs = append(outputDocs, types.ScoredDocument{
DocId: d.DocId,
TokenSnippetLocations: d.TokenSnippetLocations,
TokenLocations: d.TokenLocations})
} request.rankerReturnChannel <- rankerReturnRequest{
docs: outputDocs,
numDocs: len(outputDocs),
}
continue
} rankerRequest := rankerRankRequest{
countDocsOnly: request.countDocsOnly,
docs: docs,
options: request.options,
rankerReturnChannel: request.rankerReturnChannel,
}
engine.rankerRankChannels[shard] <- rankerRequest
}
}
lookup函数实现:
// 查找包含全部搜索键(AND操作)的文档
// 当docIds不为nil时仅从docIds指定的文档中查找
func (indexer *Indexer) Lookup(
tokens []string, labels []string, docIds map[uint64]bool, countDocsOnly bool) (docs []types.IndexedDocument, numDocs int) {
if indexer.initialized == false {
log.Fatal("索引器尚未初始化")
} indexer.DocInfosShard.RLock()
defer indexer.DocInfosShard.RUnlock() if indexer.DocInfosShard.NumDocuments == {
return
}
numDocs = // 合并关键词和标签为搜索键
keywords := make([]string, len(tokens)+len(labels))
copy(keywords, tokens)
copy(keywords[len(tokens):], labels) indexer.InvertedIndexShard.RLock() table := make([]*types.KeywordIndices, len(keywords))
for i, keyword := range keywords {
indices, found := indexer.InvertedIndexShard.InvertedIndex[keyword]
if !found {
// 当反向索引表中无此搜索键时直接返回
indexer.InvertedIndexShard.RUnlock()
return
} else {
// 否则加入反向表中
table[i] = indices
}
// 当没有找到时直接返回
if len(table) == {
indexer.InvertedIndexShard.RUnlock()
return
} // 归并查找各个搜索键出现文档的交集
// 从后向前查保证先输出DocId较大文档
indexPointers := make([]int, len(table))
for iTable := ; iTable < len(table); iTable++ {
indexPointers[iTable] = indexer.getIndexLength(table[iTable]) -
}
// 平均文本关键词长度,用于计算BM25
avgDocLength := indexer.InvertedIndexShard.TotalTokenLength / float32(indexer.DocInfosShard.NumDocuments)
indexer.InvertedIndexShard.RUnlock() for ; indexPointers[] >= ; indexPointers[]-- {
// 以第一个搜索键出现的文档作为基准,并遍历其他搜索键搜索同一文档
baseDocId := indexer.getDocId(table[], indexPointers[]) // 全局范围查找目标文档是否存在
if _, ok := indexer.DocInfosShard.DocInfos[baseDocId]; !ok {
// if !IsDocExist(baseDocId) {
// 文档信息中不存在反向索引文档时,跳过
// 该情况由不对称删除操作所造成
continue
} if docIds != nil {
_, found := docIds[baseDocId]
if !found {
continue
}
}
iTable :=
found := true
for ; iTable < len(table); iTable++ {
// 二分法比简单的顺序归并效率高,也有更高效率的算法,
// 但顺序归并也许是更好的选择,考虑到将来需要用链表重新实现
// 以避免反向表添加新文档时的写锁。
// TODO: 进一步研究不同求交集算法的速度和可扩展性。
position, foundBaseDocId := indexer.searchIndex(table[iTable],
, indexPointers[iTable], baseDocId)
if foundBaseDocId {
indexPointers[iTable] = position
} else {
if position == {
// 该搜索键中所有的文档ID都比baseDocId大,因此已经没有
// 继续查找的必要。
return
} else {
// 继续下一indexPointers[0]的查找
indexPointers[iTable] = position -
found = false
break
}
}
} if found {
indexedDoc := types.IndexedDocument{} // 当为LocationsIndex时计算关键词紧邻距离
if indexer.initOptions.IndexType == types.LocationsIndex {
// 计算有多少关键词是带有距离信息的
numTokensWithLocations :=
for i, t := range table[:len(tokens)] {
if len(t.Locations[indexPointers[i]]) > {
numTokensWithLocations++
}
}
if numTokensWithLocations != len(tokens) {
if !countDocsOnly {
docs = append(docs, types.IndexedDocument{
DocId: baseDocId,
})
}
numDocs++
break
}
// 计算搜索键在文档中的紧邻距离
tokenProximity, tokenLocations := computeTokenProximity(table[:len(tokens)], indexPointers, tokens)
indexedDoc.TokenProximity = int32(tokenProximity)
indexedDoc.TokenSnippetLocations = tokenLocations // 添加TokenLocations
indexedDoc.TokenLocations = make([][]int, len(tokens))
for i, t := range table[:len(tokens)] {
indexedDoc.TokenLocations[i] = t.Locations[indexPointers[i]]
}
} // 当为LocationsIndex或者FrequenciesIndex时计算BM25
if indexer.initOptions.IndexType == types.LocationsIndex ||
indexer.initOptions.IndexType == types.FrequenciesIndex {
bm25 := float32()
d := indexer.DocInfosShard.DocInfos[baseDocId].TokenLengths
for i, t := range table[:len(tokens)] {
var frequency float32
if indexer.initOptions.IndexType == types.LocationsIndex {
frequency = float32(len(t.Locations[indexPointers[i]]))
} else {
frequency = t.Frequencies[indexPointers[i]]
} // 计算BM25
if len(t.DocIds) > && frequency > && indexer.initOptions.BM25Parameters != nil && avgDocLength != {
// 带平滑的idf
idf := float32(math.Log2(float64(indexer.DocInfosShard.NumDocuments)/float64(len(t.DocIds)) + ))
k1 := indexer.initOptions.BM25Parameters.K1
b := indexer.initOptions.BM25Parameters.B
bm25 += idf * frequency * (k1 + ) / (frequency + k1*(-b+b*d/avgDocLength))
}
}
indexedDoc.BM25 = float32(bm25)
} indexedDoc.DocId = baseDocId
if !countDocsOnly {
docs = append(docs, indexedDoc)
}
numDocs++
}
}
return
}
wukong引擎源码分析之搜索——docid有序的数组里二分归并求交集,如果用跳表的话,在插入索引时会更快的更多相关文章
-
wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储
searcher.IndexDocument(0, types.DocumentIndexData{Content: "此次百度收购将成中国互联网最大并购"}) engine.go ...
-
wukong引擎源码分析之索引——part 3 文档评分 无非就是将docid对应的fields信息存储起来,为搜索结果rank评分用
之前的文章分析过,接受索引请求处理的代码在segmenter_worker.go里: func (engine *Engine) segmenterWorker() { for { request : ...
-
wukong引擎源码分析之索引——part 2 持久化 直接set(key,docID数组)在kv存储里
前面说过,接收indexerRequest的代码在index_worker.go里: func (engine *Engine) indexerAddDocumentWorker(shard int) ...
-
【OpenCV】SIFT原理与源码分析:关键点搜索与定位
<SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html 由前一步<DoG尺度空间构造>,我们得到了 ...
-
Elasticsearch源码分析—线程池(十一) ——就是从队列里处理请求
Elasticsearch源码分析—线程池(十一) 转自:https://www.felayman.com/articles/2017/11/10/1510291570687.html 线程池 每个节 ...
-
【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据
一.问题场景模拟问题:第二次查询和第一次查询结果一模一样,没有查询出我新插入的数据 猜测:第二次查询走了Mybatis缓存 疑问:那为什么会走缓存呢? 1.service方法 @Override @T ...
-
转:Irrlicht 0.1引擎源码分析与研究(一)
目录(?)[-] 主要技术特性 引擎概览 Irrlicht的窗口管理 Irrlicht引擎主要是由一个名叫Nikolaus Gebhardt奥地利人所设计,是sourceforge上的一个开源项目 ...
-
MQTT再学习 -- MQTT 客户端源码分析
MQTT 源码分析,搜索了一下发现网络上讲的很少,多是逍遥子的那几篇. 参看:逍遥子_mosquitto源码分析系列 参看:MQTT libmosquitto源码分析 参看:Mosquitto学习笔记 ...
-
ArrayList 源码分析和自定义ArrayList实现
概述 ArrayList 是基于数组实现的,是一个能自动扩展的动态数组. ArrayList 是线程不安全的,多线程情况下添加元素会出现数组越界的情况,而且数组赋值操作不是原子操作,会导致多线程情况下 ...
随机推荐
-
Python 练习册--生成唯一激活码(邀请码)
题目是这样子的: 做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用生成激活码(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券)? 分析 其实要生成 ...
-
chardet安装
1.下载 chardet-2.2.1.tar.gz (md5) https://pypi.python.org/pypi/chardet#downloads 2.解压至C:\Python27\Li ...
-
EL标签库
首先要导入jar包 jst1.jar standard.jar 在页面中引入标签库 <%@taglib uri="..." prefix=".."%& ...
-
Headfirst设计模式的C++实现——状态模式(State)
state.h #ifndef _STATE_H_ #define _STATE_H_ class GumballMachine; class State { public: ; ; ; ; Stat ...
-
CentOS 漏洞修补
以前没注意 今后得实时更新系统漏洞和补丁了! 1.Bash软件安全漏洞检测及解决方案 http://netsecurity.51cto.com/art/201409/452322.htm
-
ubuntu中KDE与GNOME安装切换
转载:http://apps.hi.baidu.com/share/detail/18919303 1.在Ubuntu中安装KDE桌面命令 sudo apt-get install kUbuntu-d ...
-
Liunx-常用命令的总结(5)
cd ../dir 上一节目录下dir目录 cd - 返回上次目录 ifconfig 查看IP地址 sudo if ...
-
wx:for修改样式
在获取文字识别数据之后,对数据进行wx:for循环加了边框如图效果: 需求:点击不同边框获取不同文字,再次点击取消选中:选中背景为#999: <view wx:for="{{img_d ...
-
vs2015 编译google v8
转自:http://blog.csdn.net/runningman2012/article/details/54692010 系统Win10 64位,vs2015 1. git 下载depot_to ...
-
centos运行C程序
gcc -o Hello Hello.c 编译成可执行文件 ./Hello 运行 win上也是一样