海量数据处理 - 位图法
相信大家或多或少都会看到这样的问题
-
40亿个QQ号,如何判断一个QQ号是否存在?
-
限制1GB内存,这40亿个QQ号,如何判断一个QQ号是否存在?
-
限制1GB内存,这40亿个QQ号,如何快速去重?
-
限制1GB内存,这40亿个QQ号, 如何找出所有不重复的QQ号?
首先我们以第一个问题举例,将问题拆解一下:
看到判断一个QQ号是否存在,一看到判断,我们第一时间的想法就是用Map来判断
在内存没有限制的情况下,直接把40亿QQ号一次性加载到内存,用HashMap来存着,挺好的
那现在来到第二个问题,在限制1GB内存的情况下,如何判断一个QQ号是否存在?
仔细想想,我们用HashMap存储了什么?存储了QQ号 + 出现次数对吧
当出现次数 > 0的QQ号,我们就判断这个QQ号是存在的
那我们的返回结果也就是 是 or 否,那我们存储的QQ号能否去掉呢?这就是我要说的位图法,从字节存储 -> 位存储
位图法 字节存储 -> 位存储实现
那什么是位图?就是二进制位数组(这里需要大家理清一个概念,1个byte = 8个bit)
上面我们提到了我们只需要保存QQ号是否存在的状态,那就是两种状态,是 or 否,对应计算机不就是2进制 0 or 1吗
上图可能不够清晰的能理解整个流程,具体流程如下图:
注意,真实情况是二维的,下图将二维平铺成一维数组来展示便于理解
为什么我们说这个byte[]数组是二维的?这不就是个一维数组吗?
我们将byte[]中的每一个byte拆成8个bit来用,每个字节的8个bit为一维,则byte[]为二维
那怎么样在代码层面去实现这样一个bitmap呢?
实现分析:
- 先拿到这个数字所在的byte数组的下标index,以及这个数字在这个byte[index]的bit的位置
- 如何将这个bit位置设置为1?
- 如何判断这个num所在的字节中的位是否为1?
代码实现:
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次,出现了多次怎么办?
实现分析:
代码实现:
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
}