searcher.IndexDocument(0, types.DocumentIndexData{Content: "此次百度收购将成中国互联网最大并购"})

engine.go中的源码实现:

// 将文档加入索引
//
// 输入参数:
//  docId   标识文档编号,必须唯一
//  data    见DocumentIndexData注释
//
// 注意:
//      1. 这个函数是线程安全的,请尽可能并发调用以提高索引速度
//  2. 这个函数调用是非同步的,也就是说在函数返回时有可能文档还没有加入索引中,因此
//         如果立刻调用Search可能无法查询到这个文档。强制刷新索引请调用FlushIndex函数。
func (engine *Engine) IndexDocument(docId uint64, data types.DocumentIndexData) {if !engine.initialized {log.Fatal("必须先初始化引擎")}atomic.AddUint64(&engine.numIndexingRequests, 1)shard := int(murmur.Murmur3([]byte(fmt.Sprint("%d", docId))) % uint32(engine.initOptions.NumShards))engine.segmenterChannel <- segmenterRequest{docId: docId, shard: shard, data: data}
}

而其中:

engine.segmenterChannel <- segmenterRequest{docId: docId, shard: shard, data: data}

将请求发送给segmenterChannel,其定义:

    // 建立分词器使用的通信通道segmenterChannel chan segmenterRequest

而接受请求处理的代码在segmenter_worker.go里:

func (engine *Engine) segmenterWorker() {for {request := <-engine.segmenterChannel //关键tokensMap := make(map[string][]int)numTokens := 0if !engine.initOptions.NotUsingSegmenter && request.data.Content != "" {// 当文档正文不为空时,优先从内容分词中得到关键词segments := engine.segmenter.Segment([]byte(request.data.Content))for _, segment := range segments {token := segment.Token().Text()if !engine.stopTokens.IsStopToken(token) {tokensMap[token] = append(tokensMap[token], segment.Start())}}numTokens = len(segments)} else {// 否则载入用户输入的关键词for _, t := range request.data.Tokens {if !engine.stopTokens.IsStopToken(t.Text) {tokensMap[t.Text] = t.Locations}}numTokens = len(request.data.Tokens)}// 加入非分词的文档标签for _, label := range request.data.Labels {if !engine.initOptions.NotUsingSegmenter {if !engine.stopTokens.IsStopToken(label) {tokensMap[label] = []int{}}} else {tokensMap[label] = []int{}}}indexerRequest := indexerAddDocumentRequest{document: &types.DocumentIndex{DocId:       request.docId,TokenLength: float32(numTokens),Keywords:    make([]types.KeywordIndex, len(tokensMap)),},}iTokens := 0for k, v := range tokensMap {indexerRequest.document.Keywords[iTokens] = types.KeywordIndex{Text: k,// 非分词标注的词频设置为0,不参与tf-idf计算
                Frequency: float32(len(v)),Starts:    v}iTokens++}var dealDocInfoChan = make(chan bool, 1)indexerRequest.dealDocInfoChan = dealDocInfoChanengine.indexerAddDocumentChannels[request.shard] <- indexerRequestrankerRequest := rankerAddDocRequest{docId:           request.docId,fields:          request.data.Fields,dealDocInfoChan: dealDocInfoChan,}engine.rankerAddDocChannels[request.shard] <- rankerRequest}
}

上面代码的作用就是在统计词频和单词位置(注意:tag也是作为搜索的单词,不过其词频是0,而无法参与tf-idf计算),并封装为indexerRequest,发送给engine.indexerAddDocumentChannels[request.shard]

------------------------------------------------

补充一点,上述代码之所以得以执行是因为在:

searcher = engine.Engine{}
// 初始化
searcher.Init(types.EngineInitOptions{SegmenterDictionaries: "../data/dictionary.txt"})

searcher的初始化代码里有这么一段:

    // 启动分词器for iThread := 0; iThread < options.NumSegmenterThreads; iThread++ {go engine.segmenterWorker()}

------------------------------------------------

接收indexerRequest的代码在index_worker.go里:

func (engine *Engine) indexerAddDocumentWorker(shard int) {for {request := <-engine.indexerAddDocumentChannels[shard] //关键addInvertedIndex := engine.indexers[shard].AddDocument(request.document, request.dealDocInfoChan)// saveif engine.initOptions.UsePersistentStorage {for k, v := range addInvertedIndex {engine.persistentStorageIndexDocumentChannels[shard] <- persistentStorageIndexDocumentRequest{typ:            "index",keyword:        k,keywordIndices: v,}}}atomic.AddUint64(&engine.numTokenIndexAdded,uint64(len(request.document.Keywords)))atomic.AddUint64(&engine.numDocumentsIndexed, 1)}
}

-----------------------------------------------

而上述函数之所以得以执行,还是因为在searcher的初始化函数里有这么一句:

        // 启动索引器和排序器for shard := 0; shard < options.NumShards; shard++ {go engine.indexerAddDocumentWorker(shard) //关键go engine.indexerRemoveDocWorker(shard)go engine.rankerAddDocWorker(shard)go engine.rankerRemoveDocWorker(shard)for i := 0; i < options.NumIndexerThreadsPerShard; i++ {go engine.indexerLookupWorker(shard)}for i := 0; i < options.NumRankerThreadsPerShard; i++ {go engine.rankerRankWorker(shard)}}

------------------------------------------------

其中,engine.indexers[shard].AddDocument(request.document, request.dealDocInfoChan)的核心代码在indexer.go里:

// 向反向索引表中加入一个文档
func (indexer *Indexer) AddDocument(document *types.DocumentIndex, dealDocInfoChan chan<- bool) (addInvertedIndex map[string]*types.KeywordIndices) {if indexer.initialized == false {log.Fatal("索引器尚未初始化")}indexer.InvertedIndexShard.Lock()defer indexer.InvertedIndexShard.Unlock()// 更新文档总数及关键词总长度
    indexer.DocInfosShard.Lock()if _, found := indexer.DocInfosShard.DocInfos[document.DocId]; !found {indexer.DocInfosShard.DocInfos[document.DocId] = new(types.DocInfo)indexer.DocInfosShard.NumDocuments++}if document.TokenLength != 0 {originalLength := indexer.DocInfosShard.DocInfos[document.DocId].TokenLengthsindexer.DocInfosShard.DocInfos[document.DocId].TokenLengths = float32(document.TokenLength)indexer.InvertedIndexShard.TotalTokenLength += document.TokenLength - originalLength}indexer.DocInfosShard.Unlock()close(dealDocInfoChan)// docIdIsNew := truefoundKeyword := falseaddInvertedIndex = make(map[string]*types.KeywordIndices)for _, keyword := range document.Keywords {addInvertedIndex[keyword.Text], foundKeyword = indexer.InvertedIndexShard.InvertedIndex[keyword.Text]if !foundKeyword {addInvertedIndex[keyword.Text] = new(types.KeywordIndices)}indices := addInvertedIndex[keyword.Text]if !foundKeyword {// 如果没找到该搜索键则加入switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations = [][]int{keyword.Starts}case types.FrequenciesIndex:indices.Frequencies = []float32{keyword.Frequency}}indices.DocIds = []uint64{document.DocId}indexer.InvertedIndexShard.InvertedIndex[keyword.Text] = indicescontinue}// 查找应该插入的位置position, found := indexer.searchIndex(indices, 0, indexer.getIndexLength(indices)-1, document.DocId)if found {// docIdIsNew = false// 覆盖已有的索引项switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations[position] = keyword.Startscase types.FrequenciesIndex:indices.Frequencies[position] = keyword.Frequency}continue}// 当索引不存在时,插入新索引项switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations = append(indices.Locations, []int{})copy(indices.Locations[position+1:], indices.Locations[position:])indices.Locations[position] = keyword.Startscase types.FrequenciesIndex:indices.Frequencies = append(indices.Frequencies, float32(0))copy(indices.Frequencies[position+1:], indices.Frequencies[position:])indices.Frequencies[position] = keyword.Frequency}indices.DocIds = append(indices.DocIds, 0)copy(indices.DocIds[position+1:], indices.DocIds[position:])indices.DocIds[position] = document.DocId}return
}

查找docID是否存在于倒排列表的时候是二分:

// 二分法查找indices中某文档的索引项
// 第一个返回参数为找到的位置或需要插入的位置
// 第二个返回参数标明是否找到
func (indexer *Indexer) searchIndex( indices *types.KeywordIndices, start int, end int, docId uint64) (int, bool) {// 特殊情况if indexer.getIndexLength(indices) == start {return start, false}if docId < indexer.getDocId(indices, start) {return start, false} else if docId == indexer.getDocId(indices, start) {return start, true}if docId > indexer.getDocId(indices, end) {return end + 1, false} else if docId == indexer.getDocId(indices, end) {return end, true}// 二分var middle intfor end-start > 1 {middle = (start + end) / 2      if docId == indexer.getDocId(indices, middle) {return middle, true} else if docId > indexer.getDocId(indices, middle) {start = middle     } else {end = middle}}return end, false
}  

TODO,待分析:indexer里索引的细节,以及评分相关的逻辑:

rankerRequest := rankerAddDocRequest{docId:           request.docId,fields:          request.data.Fields,dealDocInfoChan: dealDocInfoChan,}engine.rankerAddDocChannels[request.shard] <- rankerRequest

转载于:https://www.cnblogs.com/bonelee/p/6528556.html

wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储相关推荐

  1. wukong引擎源码分析之索引——part 2 持久化 直接set(key,docID数组)在kv存储里...

    前面说过,接收indexerRequest的代码在index_worker.go里: func (engine *Engine) indexerAddDocumentWorker(shard int) ...

  2. wukong引擎源码分析之索引——part 3 文档评分 无非就是将docid对应的fields信息存储起来,为搜索结果rank评分用...

    之前的文章分析过,接受索引请求处理的代码在segmenter_worker.go里: func (engine *Engine) segmenterWorker() {for {request := ...

  3. wukong引擎源码分析之搜索——docid有序的数组里二分归并求交集,如果用跳表的话,在插入索引时会更快...

    searcher.Search(types.SearchRequest{Text: "百度中国"}) // 查找满足搜索条件的文档,此函数线程安全 func (engine *En ...

  4. 虚幻引擎源码分析(5)

    虚幻引擎源码分析(5)

  5. bleve搜索引擎源码分析之索引——mapping和lucene一样,也有_all

    例子: package mainimport ("fmt""github.com/blevesearch/bleve" )func main() {// ope ...

  6. 白鹭php源码,egret 2D引擎源码分析(二) 创建播放器

    本帖最后由 fightingcat 于 2016-7-16 00:26 编辑 上一篇讲到了引擎的入口runEgret为每一个播放器标签(就是index.html中看到的那个 之前web.WebPlay ...

  7. bleve搜索引擎源码分析之索引——mapping真复杂啊

    接下来看看下面index部分的源码实现: data := struct {Name stringDes string}{Name: "hello world this is bone&quo ...

  8. 悟空分词与mysql结合_悟空分词的搜索和排序源码分析之——索引

    转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/ 索引过程分析 下面我们来分析索引过程. // 将文档加入索引 // // ...

  9. 以太坊共识引擎源码分析

    这一篇分析以太坊的共识引擎,先看一下各组件之间的关系: Engine接口定义了共识引擎需要实现的所有函数,实际上按功能可以划分为2类: 区块验证类:以Verify开头,当收到新区块时,需要先验证区块的 ...

最新文章

  1. Linux网络属性配置相关命令
  2. Synchronize读脏
  3. Fiori elements执行过程解析:When click go in table list, odata service is sent
  4. 一根网线有这么多“花样”,你知道吗?
  5. python创建和控制的实体称为_Python语法基础
  6. 通过DOS命令nslookup查域名DNS服务器
  7. 白帽黑客眼中的网络安全 挡黑客财路曾收恐吓信
  8. MySQL建表(那些字段必须)命令详解
  9. POJ 2457 BFS
  10. android 选择银行类型,『自定义View实战』—— 银行种类选择器
  11. 18.docker top
  12. 机器学习(周志华) 第七章贝叶斯分类器
  13. IDEA自定义带JavaDoc的getter/setter模板
  14. JavaScript级联链表
  15. 毕业设计周报(第六周)
  16. python使用turtle库绘制一个红色五角星_使用turtle库绘制红色五角星图形
  17. Sybase数据库自动备份的实现
  18. 排名:百度小程序 微信 + 支付宝 + 百度 + 头条 商城源码-拓客营销
  19. SAP 发票金额容差与供应商容差
  20. R语言入门教程知识 第一章 R语言

热门文章

  1. 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
  2. 在Linux下怎样让top命令启动之后就按内存使用排序(或CPU使用排序)?
  3. ubuntu05.04 linux2.6.10 内核安装
  4. c语言邻接表的构建_c语言数据结构--图的邻接矩阵和邻接表操作的基本操作
  5. linux搜索有哪些文件夹,Linux常见几个查找命令
  6. java 主线程_Java中的主线程 - Break易站
  7. 原生mysql的批量更新及性能测试
  8. 字节Java高级岗:javaio流面试题
  9. 深度学习生态圈【详解深度学习工具Keras】
  10. 【Java Web前端开发】Response笔记