后端场景优化 - 海量数据处理场景(位图法)

259 阅读3分钟

海量数据处理 - 位图法

相信大家或多或少都会看到这样的问题

  1. 40亿个QQ号,如何判断一个QQ号是否存在?

  2. 限制1GB内存,这40亿个QQ号,如何判断一个QQ号是否存在?

  3. 限制1GB内存,这40亿个QQ号,如何快速去重?

  4. 限制1GB内存,这40亿个QQ号, 如何找出所有不重复的QQ号?

首先我们以第一个问题举例,将问题拆解一下:

看到判断一个QQ号是否存在,一看到判断,我们第一时间的想法就是用Map来判断

image.png

在内存没有限制的情况下,直接把40亿QQ号一次性加载到内存,用HashMap来存着,挺好的

那现在来到第二个问题,在限制1GB内存的情况下,如何判断一个QQ号是否存在?

仔细想想,我们用HashMap存储了什么?存储了QQ号 + 出现次数对吧

当出现次数 > 0的QQ号,我们就判断这个QQ号是存在的

那我们的返回结果也就是 是 or 否,那我们存储的QQ号能否去掉呢?这就是我要说的位图法,从字节存储 -> 位存储

image.png

位图法 字节存储 -> 位存储实现

那什么是位图?就是二进制位数组(这里需要大家理清一个概念,1个byte = 8个bit)

上面我们提到了我们只需要保存QQ号是否存在的状态,那就是两种状态,是 or 否,对应计算机不就是2进制 0 or 1吗

image.png

上图可能不够清晰的能理解整个流程,具体流程如下图:

注意,真实情况是二维的,下图将二维平铺成一维数组来展示便于理解

为什么我们说这个byte[]数组是二维的?这不就是个一维数组吗?

我们将byte[]中的每一个byte拆成8个bit来用,每个字节的8个bit为一维,则byte[]为二维

image.png

那怎么样在代码层面去实现这样一个bitmap呢?

实现分析:

  • 先拿到这个数字所在的byte数组的下标index,以及这个数字在这个byte[index]的bit的位置

image.png

  • 如何将这个bit位置设置为1?

image.png

  • 如何判断这个num所在的字节中的位是否为1?

image.png

代码实现:

type BitMap struct {
	MaxValue uint32
	ByteSlice []byte
}

func NewBitMap(maxValue uint32) *BitMap {
	return &BitMap{
		MaxValue: maxValue,
		ByteSlice: make([]byte,(maxValue + 8) / 8),
	}
}

func (bitmap *BitMap) SetNum(num uint32) {
	byteIndex := num / 8;
	bSlice := bitmap.ByteSlice[byteIndex]
	bitIndex := num % 8
	bitmap.ByteSlice[bitIndex] = bSlice | (1 << bitIndex)
}

func (bitmap * BitMap) IsExists(num uint32) bool {
	if num > bitmap.MaxValue {
		return false
	}
	byteIndex := num / 8
	bSlice := bitmap.ByteSlice[byteIndex]
	bitIndex := num % 8
	return bSlice & (1 << bitIndex) != 0
}

此时我们再回到第三个问题,如何快速去重?直接把bitmap中为1的QQ号取出来就行了,这样就不会有重复了

如何找出所有不重复的QQ号?

根据我们上面讲到的bitmap位图法可以知道,我们只能判断一个QQ号出现0次还是出现多次

没有办法知道具体出现多少次,我们就只有0和1的状态,那次是我想要知道哪些QQ号出现了0次,出现了1次,出现了多次怎么办?

image.png

实现分析:

image.png

代码实现:

type BitMapPlus struct {
	MaxValue uint32
	ByteSlice []byte
}

func NewBitMapPlus(maxValue uint32) *BitMapPlus {
	return &BitMapPlus{
		MaxValue: maxValue,
		ByteSlice: make([]byte,((maxValue + 1) * 2 + 8) / 8),
	}
}

func (bitmap *BitMapPlus) SetNum(num uint32) {
	byteIndex := num / 4;
	bSlice := bitmap.ByteSlice[byteIndex]
	bitIndex1 := (num % 4) * 2
	bitIndex2 := (num % 4) * 2 + 1
	// 00 num没有出现过
	if (bSlice & 1 << (bitIndex1) == 0) && (bSlice & 1 << (bitIndex2) == 0) {
		bitmap.ByteSlice[byteIndex] = bSlice | (1 << bitIndex2)
		return
	}
	// 01 num出现过1次
	if (bSlice & 1 << (bitIndex1) == 0) && (bSlice & 1 << (bitIndex2) != 0) {
		bitmap.ByteSlice[byteIndex] = bSlice | (1 << bitIndex1)
		return
	}
}

func (bitmap *BitMapPlus) IsExists(num uint32) int {
	if num > bitmap.MaxValue {
		return 0
	}
	byteIndex := num / 4;
	bSlice := bitmap.ByteSlice[byteIndex]
	bitIndex1 := (num % 4) * 2
	bitIndex2 := (num % 4) * 2 + 1
	// 00 num没有出现过
	if (bSlice & 1 << (bitIndex1) == 0) && (bSlice & 1 << (bitIndex2) == 0) {
		return 0
	}
	// 01 num出现过1次
	if (bSlice & 1 << (bitIndex1) == 0) && (bSlice & 1 << (bitIndex2) != 0) {
		return 1
	}
	// 11 num出现过多次
	if bSlice & 1 << (bitIndex1) == 1 {
		return 2
	}
	return 0
}