Bitmap算法
![](https://ask.qcloudimg.com/http-save/yehe-1260737/kf0l3mbglq.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/tbu6qbjnuq.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/qzxu6wv70p.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/wpiqp1hgue.jpeg?imageView2/2/w/1620)
两个月之前——
![](https://ask.qcloudimg.com/http-save/yehe-1260737/z8xr7gtd7a.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/6arod2a2ip.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/f2qtroj8x9.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/cxs4w67s9h.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/fhvrmuekp7.jpeg?imageView2/2/w/1620)
为满足用户标签的统计需求,小灰利用Mysql设计了如下的表结构,每一个维度的标签都对应着Mysql表的一列:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/w6hadvycrr.jpeg?imageView2/2/w/1620)
要想统计所有90后的程序员该怎么做呢?
用一条求交集的SQL语句即可:
Select count(distinct Name) as 用户数 from table whare age = '90后' and Occupation = '程序员' ;
要想统计所有使用苹果手机或者00后的用户总合该怎么做?
用一条求并集的SQL语句即可:
Select count(distinct Name) as 用户数 from table whare Phone = '苹果' or age = '00后' ;
![](https://ask.qcloudimg.com/http-save/yehe-1260737/lzdn20pb7f.jpeg?imageView2/2/w/1620)
两个月之后——
![](https://ask.qcloudimg.com/http-save/yehe-1260737/6dcqc7qdxi.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/uerzdwnaxn.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/b80azx8rr4.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/12mn0heq53.jpeg?imageView2/2/w/1620)
———————————————
![](https://ask.qcloudimg.com/http-save/yehe-1260737/9l00ontenp.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/jft6b9m1rw.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/c0alumhes4.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/7etsdlhtet.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/knwte9uwh6.jpeg?imageView2/2/w/1620)
1. 给定长度是10的bitmap,每一个bit位分别对应着从0到9的10个整型数。此时bitmap的所有位都是0。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ynak4i7f1i.png?imageView2/2/w/1620)
2. 把整型数4存入bitmap,对应存储的位置就是下标为4的位置,将此bit置为1。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/tm65yy8ye3.png?imageView2/2/w/1620)
3. 把整型数2存入bitmap,对应存储的位置就是下标为2的位置,将此bit置为1。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/3txpfy7s3f.png?imageView2/2/w/1620)
4. 把整型数1存入bitmap,对应存储的位置就是下标为1的位置,将此bit置为1。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/qd7iscbvmf.png?imageView2/2/w/1620)
5. 把整型数3存入bitmap,对应存储的位置就是下标为3的位置,将此bit置为1。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/wtcs726gid.png?imageView2/2/w/1620)
要问此时bitmap里存储了哪些元素?显然是4,3,2,1,一目了然。
Bitmap不仅方便查询,还可以去除掉重复的整型数。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/jgn5a5kdls.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/bj2ktm462f.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/js5qhhezxa.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/6u605jsccg.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/1758w7qe48.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/dhohx5o7mb.jpeg?imageView2/2/w/1620)
1. 建立用户名和用户ID的映射:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/xfqemsmzq8.png?imageView2/2/w/1620)
2. 让每一个标签存储包含此标签的所有用户ID,每一个标签都是一个独立的Bitmap。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/hewrmljft9.png?imageView2/2/w/1620)
3. 这样,实现用户的去重和查询统计,就变得一目了然:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/2v8i2f3hmx.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/h3gxwt40dv.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/sqpqc05qkf.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/e17jdbal9z.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/otkexopocy.jpeg?imageView2/2/w/1620)
1. 如何查找使用苹果手机的程序员用户?
![](https://ask.qcloudimg.com/http-save/yehe-1260737/goabakbobo.png?imageView2/2/w/1620)
2. 如何查找所有男性或者00后的用户?
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ysob776sc3.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/zkto5k1kru.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/6f9lpi0dgz.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ronshz0y06.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/4du790kg83.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/umzdrz7jav.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ccqt7l6223.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/x6sfw65cbs.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/793l4kbmfg.jpeg?imageView2/2/w/1620)
一周之后......
![](https://ask.qcloudimg.com/http-save/yehe-1260737/qhqt8zuzm7.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/amij4ulmxl.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/zzcha82414.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/h4d8yk575o.jpeg?imageView2/2/w/1620)
我们以上一期的用户数据为例,用户基本信息如下。按照年龄标签,可以划分成90后、00后两个Bitmap:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/92dym2cs0m.png?imageView2/2/w/1620)
用更加形象的表示,90后用户的Bitmap如下:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/83vxtnvui8.png?imageView2/2/w/1620)
这时候可以直接求得非90后的用户吗?直接进行非运算?
![](https://ask.qcloudimg.com/http-save/yehe-1260737/n8sxh3l6wh.png?imageView2/2/w/1620)
显然,非90后用户实际上只有1个,而不是图中得到的8个结果,所以不能直接进行非运算。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/p8xcb1jd56.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/kdw6jh8urt.jpeg?imageView2/2/w/1620)
同样是刚才的例子,我们给定90后用户的Bitmap,再给定一个全量用户的Bitmap。最终要求出的是存在于全量用户,但又不存在于90后用户的部分。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/35i8cnxk3f.png?imageView2/2/w/1620)
如何求出呢?我们可以使用异或操作,即相同位为0,不同位为1。
![](https://ask.qcloudimg.com/http-save/yehe-1260737/trdaisjeb5.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/cxhxaokvrr.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/l03dtkxrrn.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/o8z8gofbox.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/8ce43fitcg.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/n8g2jlwy88.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/us2jzm4vhg.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/cbiydauugs.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/c8zvnzhx45.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/yb2xqmf6il.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/3gp8u0b2v1.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/tuwdw4sejj.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/600pcq0usy.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/xvam1gcpm1.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/0b611y28x6.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/butw4xjnpm.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/2mlthxpj9o.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/glh8qmlwes.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/d2geuv9ihf.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/vhzilmohbg.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/euju5y9943.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/luusfyppb5.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/0tp5av7yzm.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/jszaqyzckh.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/2rmhmjj3zu.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/h3i30i8nnk.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/k6jmxo4jcg.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/j0ca5upfq4.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ytc29267u5.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/x7n9h8irv4.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/890431c0m3.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/t04iij7spu.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/iyfsq3r03c.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/ru7t84l2y2.png?imageView2/2/w/1620)
25769803776L = 11000000000000000000000000000000000B
8589947086L = 1000000000000000000011000011001110B
![](https://ask.qcloudimg.com/http-save/yehe-1260737/kolsm0reu9.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/i4xkgcds0c.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/yu9dbtvm5y.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/4ix2tr0dhe.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/3uirw5abfl.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/py1wf99okj.jpeg?imageView2/2/w/1620)
1.解析Word0,得知当前RLW横跨的空Word数量为0,后面有连续3个普通Word。
2.计算出当前RLW后方连续普通Word的最大ID是 64 X (0 + 3) -1 = 191。
3. 由于 191 < 400003,所以新ID必然在下一个RLW(Word4)之后。
4.解析Word4,得知当前RLW横跨的空Word数量为6247,后面有连续1个普通Word。
5.计算出当前RLW(Word4)后方连续普通Word的最大ID是191 + (6247 + 1)X64 = 400063。
6.由于400003 < 400063,因此新ID 400003的正确位置就在当前RLW(Word4)的后方普通Word,也就是Word5当中。
最终插入结果如下:
![](https://ask.qcloudimg.com/http-save/yehe-1260737/pb8ryrfsyj.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/u9n9o6bxgn.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/3luar7tmql.jpeg?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/t34coiues2.png?imageView2/2/w/1620)
![](https://ask.qcloudimg.com/http-save/yehe-1260737/slcepeffxq.jpeg?imageView2/2/w/1620)
官方说明如下:
* Though you can set the bits in any order (e.g., set(100), set(10), set(1),
* you will typically get better performance if you set the bits in increasing order (e.g., set(1), set(10), set(100)).
*
* Setting a bit that is larger than any of the current set bit
* is a constant time operation. Setting a bit that is smaller than an
* already set bit can require time proportional to the compressed
* size of the bitmap, as the bitmap may need to be rewritten.
![](https://ask.qcloudimg.com/http-save/yehe-1260737/2t1qyb1uv3.jpeg?imageView2/2/w/1620)
几点说明:
1. 该项目最初的技术选型并非Mysql,而是内存数据库hana。本文为了便于理解,把最初的存储方案写成了Mysq数据库。
1.文中介绍的Bitmap优化方法在一定程度上做了简化,源码中的逻辑要复杂得多。比如对于插入数据400003的定位,和实际步骤是有出入的。
2.如果同学们有兴趣,可以亲自去阅读源码,甚至是尝试实现自己的Bitmap算法。虽然要花不少时间,但这确实是一种很好的学习方法。
EWAHCompressedBitmap对应的maven依赖如下:
<dependency>
<groupId>com.googlecode.javaewah</groupId>
<artifactId>JavaEWAH</artifactId>
<version>1.1.0</version>
</dependency>