BitMap是个啥

267 阅读4分钟

BitMap 主要用来处理大文件场景下的数据搜索、去重、排序。

1、MySQL中的应用

那一个简单的例子来说吧,假设有一个用户表,和一堆的属性:

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
  `user_name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',
  `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
  `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态',
  `create_time` int(10) NOT NULL DEFAULT '0' COMMENT '创建时间',
  `update_time` int(10) NOT NULL DEFAULT '0' COMMENT '更新时间',
  PRIMARY KEY (`id`),
  key user_id (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';

有如下用户属性:
程序员、司机、90后、秃头大叔、爱打篮球、爱打游戏、爱吃辣...

我们如果将这些属性都关联到用户UID上呢?比较常规的作答就是给每个属性分配一个ID

tagId = 1:程序员
tagId = 2:司机
tagId = 3:90后
tagId = 4:秃头大叔
tagId = 5:爱打篮球
tagId = 6:爱打游戏
tagId = 7:爱吃辣
......

方案1:在user表增加一个tagIds的字段,存储格式为 1,3,6,7 或者 [1,3,6,7]
      如果要进行筛选包含tagId=6的数据,需要进行find_in_set等函数进行比较。

方案2:另外新建一个关联表userTags,有多少标签就insert多少条关联数据

以上两个方案还存在一个问题:如果是做图像识别等场景,每个用户可能有成千上万个标签,数据量会较大

那么如何节约存储呢?这个时候可以考虑下BitMap:

假设预先开启N个 bit 位,如图,默认每个 bit 位的值都是0


用户拥有某个 tagId 的属性,就将对应 bit 的结果置为 1,假设某用户属性ID为1,3,6,7


原理很容易理解,如果转到MySQL字段中如何存储?

方案1:按照字符默认开辟 00000000000, 将对应位置1后,存储varchar为00001100101(前置0可省)

方案2:假设上面表格为二进制数据,如果选择了tagId:1,3,6,7。对应二进制如下:
                    00000000001
                    00000000100
                    00000100000
                    00001000000
或运算结果为:         00001100101
转换为10进制为:       2^6 + 2^5 + 2^2 + 2^0 = 101 
                    (由于存在 2^0 = 1,在设计时也可直接舍弃该位从 2^1 开始计算)
对应数据库内存储的值为:101

对于以上两个方案,其实都存在一个问题:如果需要筛选用户标签包含某属性的ID如果去做?

方案1:可能存储的字符会很多。
      有64个标签就要开辟 varchat(64) 用来存储,如果用bigint也就能存储20个标签
      而且筛选会很难处理,比如筛选爱打游戏的用户,即如何判断某位上的值是否为1?

方案2:转为整数后存储的字符会很少,但也不能超过64个标签,因为bigint最大为2^64
      对于筛选,比如筛选爱打游戏的用户:先列举出爱打游戏标签的二进制 00000100101,
      依次拿所有的数据进行异或运算,如果与运算之后的结果为:00000100000(十进制32)即为符合条件。
       
      映射到SQL为以下两种写法: 
            where tagIds & 32 > 0; (32对应二进制 2^5)
            where tagIds & 32 = 32;
但 BitMap 转10进制存储后仍有一个问题无法解决,where查询无法命中索引

那么如何解决MySQL这种查询场景的弊端呢?

其实可以考虑用ES存储为json,然后通过ES自带的高效匹配进行处理,关于ES的介绍我们在另外一篇博客有介绍:juejin.cn/post/684490…

2、实际场景中的应用

简单列举一个场景:很多时候面试都会问的一个问题,已知某文件内包含一些电话号码,每个号码为8位数字,统计去重后号码的个数。

思考:一个字节(1B)可以存储8个bit位,对应一个long类型的整数。

常见的存储:一个电话号码需要 1B ,1M大约可存储 105W 个电话号码,如果数据量较少,我们直接开辟内存直接存储即可,假设文件内的号码是亿级别的如何处理?

考虑下BitMap:8位整数不超过99999999,需开辟 99999999 个bit 位,每个bit位只存储0/1。

即 99999999 bit = 99999999/8 = 12500000 B = 12500000 / 1024 = 12207 KB = 12 MB 

这样我们只需要12MB就能完成亿+级别的所有电话号码的存储。

其实 Bloom Filter 也是基于 BitMap 算法的一种扩展,关于Bloom Filter 我们在另一篇博客中有介绍:juejin.cn/post/684490…