解析倒排索引

什么是倒排索引

倒排索引(Inverted Index)是一种数据结构,主要用于高效地存储和检索文档中的词项。与正排索引(Forward Index)不同,正排索引是将每个文档ID对应多个内容,而倒排索引则是将多个关键词对应到一个或多个文档ID。这种结构特别适合文本检索和搜索引擎的需求,因为它可以快速定位到包含特定词项的文档。

倒排索引的优势

对比其他索引, 倒排索引有以下几种优势

  • 快速检索:可以在较短的时间内返回包含特定词项的所有文档,尤其适合大规模数据集。

  • 支持复杂查询:能够处理多种类型的查询,包括短语查询、布尔查询等,满足用户的不同需求。

  • 高效的空间使用:在许多应用中,倒排索引能比正排索引更高效地使用存储空间,特别是当文档中包含大量重复词项时。

  • 扩展性:随着文档和词项数量的增加,倒排索引仍能保持良好的性能,适应大规模数据的需求。

倒排索引的应用场景

倒排索引广泛应用在需要根据文本查找的场景下, 如:

  • 搜索引擎:如 Google、Bing 等利用倒排索引快速返回与用户查询相关的网页。
  • 文档检索系统:如数字图书馆、电子邮件搜索等,帮助用户快速找到所需文档。
  • 信息检索:在自然语言处理(NLP)应用中,倒排索引常用于信息提取和文本分析。

如何构建倒排索引

文档收集:首先,收集一组需要建立索引的文档,通常是一个文本库或数据库中的记录。

文本预处理

  • 分词:将文档中的文本分解成单个词项,通常会去除停用词(如“的”、“是”等)以及进行词形还原(如“running”转为“run”)。
  • 规范化:可以将所有词项转换为小写,去除标点符号等。

构建索引

  • 创建一个空的倒排索引结构。
  • 遍历每个文档,对于每个词项:
  • 如果词项在索引中不存在,则添加该词项,并初始化其文档列表。
  • 将当前文档ID添加到词项的文档列表中。
  • 更新文档频率。
  • 存储和优化:将最终的倒排索引存储在数据库或文件中,并进行压缩和优化以节省空间。例如,可以使用位图压缩等技术。

代码实现倒排索引

以下是用go语言的一个倒排索引, 存储索引用的是bitmap, bitmap有着快速查找, 集合快速求并交集的特点, 特别适合来实现倒排索引

  1. 结构体的定义, table负责存储每个关键词对应的bitmap, locks为每个关键词的读写锁, 保证并发读写安全, data存储文档的数据, base是 bitmap结构需要用到的字段, bitmap的实现可以看我的另一篇文章bitmap

    // 倒排索引整体上是个map,map的value是一个bitmap
    // ConcurrentHashMap 是一个并发安全的map
    type BitmapReverseIndex struct {
    	table *util.ConcurrentHashMap //分段map,并发安全
    	locks []sync.RWMutex          //修改倒排索引时,相同的key需要去竞争同一把锁
    	Data  *util.ConcurrentHashMap // 存储文档的特征值, key为IntId
    	base  int64
    }
    
    // 返回的数据
    // id 为存储的倒排索引id
    // bitsfeature 存储文档的一些特征选项, 每个bit表示一个变量
    type BitmapValue struct {
    	Id          string
    	BitsFeature uint64
    }
    
    // DocNumEstimate是预估的doc数量
    func NewBitmapReverseIndex(DocNumEstimate int) *BitmapReverseIndex {
    	indexer := new(BitmapReverseIndex)
    	indexer.table = util.NewConcurrentHashMap(runtime.NumCPU(), DocNumEstimate)
    	indexer.locks = make([]sync.RWMutex, 1000)
    	indexer.base = 64
    	return indexer
    }
    
  2. 获取key对应的锁

    func (indexer BitmapReverseIndex) getLock(key string) *sync.RWMutex {
    	n := int(farmhash.Hash32WithSeed([]byte(key), 0))
    	return &indexer.locks[n%len(indexer.locks)]
    }
    
  3. 添加文档

    func (indexer *BitmapReverseIndex) Add(doc types2.Document) {
    	for _, keyword := range doc.Keywords {
    		key := keyword.ToString()
    		lock := indexer.getLock(key)
    		lock.Lock()
    		bitmapValue := BitmapValue{doc.Id, doc.BitsFeature}
    		if value, exists := indexer.table.Get(key); exists {
    			bm := value.(*util.Bitmap)
    
    			err := bm.Set(strconv.FormatUint(doc.IntId, 10))
    			if err != nil {
    				return
    			}
    			indexer.Data.Set(strconv.FormatUint(doc.IntId, 10), bitmapValue)
    		} else {
    			bm := util.NewBitmap(indexer.base)
    			bm.Set(strconv.FormatUint(doc.IntId, 10))
    			indexer.Data.Set(strconv.FormatUint(doc.IntId, 10), bitmapValue)
    			indexer.table.Set(key, bm)
    		}
    		// util.Log.Printf("add key %s value %d to reverse index\n", key, DocId)
    		lock.Unlock()
    	}
    }
    
  4. 删除文档

    func (indexr *BitmapReverseIndex) Delete(IntId uint64, keyword *types2.Keyword) {
    	key := keyword.ToString()
    	lock := indexr.getLock(key)
    	lock.Lock()
    	if value, exists := indexr.table.Get(key); exists {
    		bm := value.(*util.Bitmap)
    		bm.Remove(strconv.FormatUint(IntId, 10))
    	}
    	lock.Unlock()
    }
    
  5. 对文档求交并集

    // 求多个Bitmap的交集
    func IntersectionOfBitmap(bitmaps ...*util.Bitmap) *util.Bitmap {
    	if len(bitmaps) == 0 {
    		return nil
    	}
    
    	if len(bitmaps) == 1 {
    		return bitmaps[0]
    	}
    
    	res := bitmaps[0]
    	for i := 1; i < len(bitmaps); i++ {
    		res.Intersect(bitmaps[i])
    	}
    
    	return res
    }
    
    // 求多个Bitmap的并集
    func UnionsetOfBitmap(bitmaps ...*util.Bitmap) *util.Bitmap {
    	if len(bitmaps) == 0 {
    		return nil
    	}
    	if len(bitmaps) == 1 {
    		return bitmaps[0]
    	}
    	res := bitmaps[0]
    	for i := 1; i < len(bitmaps); i++ {
    		res = res.Union(bitmaps[i])
    	}
    
    	return res
    }
    
    
  6. 筛选条件

    // 按照bits特征进行过滤
    func (indexer BitmapReverseIndex) FilterByBits(bits uint64, onFlag uint64, offFlag uint64, orFlags []uint64) bool {
    	//onFlag所有bit必须全部命中
    	if bits&onFlag != onFlag {
    		return false
    	}
    	//offFlag所有bit必须全部不命中
    	if bits&offFlag != 0 {
    		return false
    	}
    	//多个orFlags必须全部命中
    	for _, orFlag := range orFlags {
    		if orFlag > 0 && bits&orFlag <= 0 { //单个orFlag只人有一个bit命中即可
    			return false
    		}
    	}
    	return true
    }
    
  7. 搜索

    // 搜索,返回Bitmap
    func (indexer BitmapReverseIndex) search(q *types2.TermQuery, onFlag uint64, offFlag uint64, orFlags []uint64) *util.Bitmap {
    	if q.Keyword != nil {
    		Keyword := q.Keyword.ToString()
    		if value, exists := indexer.table.Get(Keyword); exists {
    			result := util.NewBitmap(indexer.base)
    			bm := value.(*util.Bitmap)
    			// util.Log.Printf("retrive %d docs by key %s", list.Len(), Keyword)
    			iterator := bm.CreateBitMapIterator()
    			node := iterator.Next()
    			for node != nil {
    				intId, _ := strconv.ParseUint(node.Data, 10, 64)
    				val, _ := indexer.Data.Get(node.Data)
    				bmv := val.(BitmapValue)
    				flag := bmv.BitsFeature
    				if intId > 0 && indexer.FilterByBits(flag, onFlag, offFlag, orFlags) { //确保有效元素都大于0
    					result.Set(node.Data)
    				}
    				node = iterator.Next()
    			}
    			return result
    		}
    	} else if len(q.Must) > 0 {
    		results := make([]*util.Bitmap, 0, len(q.Must))
    		for _, q := range q.Must {
    			results = append(results, indexer.search(q, onFlag, offFlag, orFlags))
    		}
    		return IntersectionOfBitmap(results...)
    	} else if len(q.Should) > 0 {
    		results := make([]*util.Bitmap, 0, len(q.Must))
    		for _, q := range q.Should {
    			results = append(results, indexer.search(q, onFlag, offFlag, orFlags))
    		}
    		return UnionsetOfBitmap(results...)
    	}
    	return nil
    }
    
    // 搜索,返回docId
    func (indexer BitmapReverseIndex) Search(query *types2.TermQuery, onFlag uint64, offFlag uint64, orFlags []uint64) []string {
    	result := indexer.search(query, onFlag, offFlag, orFlags)
    	if result == nil {
    		return nil
    	}
    	arr := make([]string, 0)
    	iterator := result.CreateBitMapIterator()
    	node := iterator.Next()
    	for node != nil {
    		val, _ := indexer.Data.Get(node.Data)
    		bmv := val.(BitmapValue)
    		arr = append(arr, bmv.Id)
    		node = iterator.Next()
    	}
    	return arr
    }
    
    

总结

倒排索引是一种高效的数据结构,用于快速存储和检索文档中的词项。它将多个关键词映射到包含这些词项的文档ID,特别适合文本检索和搜索引擎。相比于正排索引,倒排索引能快速响应复杂查询,利用空间更高效,并在文档和词项数量增加时保持良好的性能。广泛应用于搜索引擎、文档检索系统和自然语言处理等领域。其实现方式多样,使用位图等结构可以提高检索速度和效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ambition!6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值