bitmap算法介绍与应用

一 简介

bitmap中文名为位图算法,简单的说 就是通过下标来代替数据的唯一key,然后这个位通过0或1来代表这个数据是否存在。这么说可能还是不是很好理解,下面我举个例子说明:

给定长度是10的bitmap,每一个bit位分别对应着从0到9的10个整型数。此时bitmap的所有位都是0。注意下面的数字是下标

image

把整型数2、6、7存入bitmap,就是把下标为2、6、7的位置的bit位 置为1

image

此时的bitmap存了哪些元素? 显然是 2、6、7,很容易看出来

此时大家会感觉这个很简单,但是有什么用呢?

如果bitmap代表一个标签,比如爱好、性别之类

下标为用户的id,假如有10个人,用户的id范围是0~9,下面对于男,女,爱好游戏,爱好打球的bitmap

男:

image

女:

image

游戏:

image

打球:

image

先把上面四个bitmap处理成一个10位的二进制数

男: 1110001100

女: 0001110011

游戏: 0010100110

打球: 1010011000

此时有个需求,需要找到男生并且爱打游戏也爱打球的用户,该怎么求解?

其实大家很明显就能看出来,只需要通过求并集就可以,也就是:

男&游戏&打球

同理假如求 女生中 爱打游戏或者爱打球的就可以:

女&(游戏|打球)

所以利用bitmap特别适合求解这种标签人群的筛选。

可能有些人注意到了,似乎有一种情况,bitmap求解不了,那就是非,例如:求解不爱打游戏的,其实变化一下,也是可以求解的,需要先初始化一个全量的bitmap,也就是每一位都是1的:

全量: 1111111111

求不爱打游戏的,可以用全量和打游戏的做异或即可:

全量^游戏(可能会有人疑问,游戏不是直接能取非吗?要知道全量也不一定每一位都是1的,万一中间有空的,就不完全是1)

至此,简单介绍完了,下一节,讲一下实现。

二 怎么实现bitmap

(1) 先看下原生的bitmap
利用byte数组实现,一个byte占8个比特,算是一个长度为8的bitmap,如果我们需要扩大,就可以利用byte数组,以下是简单实现:


public class BitMap {

//保存数据的

 private byte[]bits;

//能够存储多少数据

 private int capacity;

public BitMap(int capacity){

this.capacity = capacity;

//1bit能存储8个数据,那么capacity数据需要多少个bit呢,capacity/8+1,右移3位相当于除以8

     bits =new byte[(capacity >>3 )+1];

}

public void add(int num){

// num/8得到byte[]的index

     int arrayIndex = num >>3;

// num%8得到在byte[index]的位置

     int position = num &0x07;

//将1左移position后,那个位置自然就是1,然后和以前的数据做|,这样,那个位置就替换成1了。

     bits[arrayIndex] |=1 <

}

public boolean contain(int num){

// num/8得到byte[]的index

     int arrayIndex = num >>3;

// num%8得到在byte[index]的位置

     int position = num &0x07;

//将1左移position后,那个位置自然就是1,然后和以前的数据做&,判断是否为0即可

     return (bits[arrayIndex] & (1 <

}

public void clear(int num){

// num/8得到byte[]的index

     int arrayIndex = num >>3;

// num%8得到在byte[index]的位置

     int position = num &0x07;

//将1左移position后,那个位置自然就是1,然后对取反,再与当前值做&,即可清除当前的位置了.

     bits[arrayIndex] &= ~(1 <

}

public static void main(String[] args) {

BitMap bitmap =new BitMap(100);

bitmap.add(7);

System.out.println("插入7成功");

boolean isexsit =bitmap.contain(7);

System.out.println("7是否存在:"+isexsit);

bitmap.clear(7);

isexsit =bitmap.contain(7);

System.out.println("7是否存在:"+isexsit);

}

(2) java提供的BitSet,使用起来非常简单,实例如下:


public static void main(String[] args) {

BitSet bitSet1 =new BitSet();

bitSet1.set(1);

bitSet1.set(10000);

BitSet bitSet2 =new BitSet();

bitSet2.set(1);

bitSet2.set(5);

System.out.println(bitSet1.get(1));

bitSet1.or(bitSet2);

System.out.println(bitSet1);

}

虽然简单,但是也有着显著的缺点,比如一个极端情况,一个bitmap只有两个位有数据,0和10000000,BitSet会把中间空的地方也初始化10000-1)/64 + 1个数组,这样浪费了很大的空间,其实按中间的这些空数组,完全可以通过offset的概念给压缩掉,能省下很大的空间

(3) redis的bitmap

bitmap是在redis中是大家都忽略的一个操作,说操作是因为 bitmap并不是redis中的一个数据结构,而是在已有的数据结构的扩展,从2.2.0版本就开始支持setbit,getbit,bitcount等几个bitmap相关命令。

关系redis的bitmap也有一个缺陷,跟Java原生的缺点一样,底层也没有做压缩处理,会浪费很大的空间,所以这里就不赘述了。

(4) google的JavaEWAH

个人认为JavaEWAH

算是非常好的bitmap实现了,主要因为JavaEWAH底层进行了压缩处理,所以在bitmap不是完全连续的情况下,占用空间会小,而且提供的api也比较丰富,目前JavaEWAH算是bitmap应用比较广泛的一个方案,以下是简单实例:


public static void main(String[] args) {

int[]arry1={0,1,2};

int[]arry2={0,4,5};

EWAHCompressedBitmap m1 =EWAHCompressedBitmap.bitmapOf(arry1);

EWAHCompressedBitmap m2 =EWAHCompressedBitmap.bitmapOf(arry2);

EWAHCompressedBitmap and =m1.and(m2);

EWAHCompressedBitmap or =m1.or(m2);

int[]andArray=and.toArray();

int[]orArray=or.toArray();

}

可以看出,在集合上的运算,JavaEWAH是非常简单易用的。

三 应用场景

(1) 问题引出

在运营推广的场景,一般会有一些,对于固定客群标签的app消息、短信消息等等的广告推送场景,如果按一般系统的思路,该怎么匹配这些客群呢?如果用户的标签都在一张表里,会简单些,一条sql就查出客群,但是事实上,对于一个优雅的设计,可能不会把所有标签并到一张大宽表,而且每一个标签,或者类似一组标签管理到一张表里,这时候,如果对于跨表的标签该怎么查询呢?大家可能都会想到关联查询呗。但如果N多标签,这时候关联查询几乎是不可能的了,所以这个时候就可以利用bitmap了。

(2) 如何利用?

问题来了,bitmap要有一个数字索引,而且最好是连续的,但是好多用户系统的唯一id是uuid,这个该怎么办呢?

关于这个问题其实我的方案是,给用户生成一个递增的数字流水id,把流水id和用户的唯一id映射维护到一张表里,这个流水id对于其它系统可以是不透明的,只是针对于标签系统使用的,在跑批用户标签的时候,通过用户id去流水id,并跑入相关标签表。

文字描述可能不太清晰,下面我大概表结构说明一下:

比如下面是一个性别的标签表:

流水Id标签

image

带有流水id的标签表已经有了,下一步就是生成Bitmap了,由于Bitmap本质上是用一个bit位来标记某个元素,所以对于性别这个标签,是延伸出两个Bitmap的,分别是男、女

生成男、女这两个Bitmap也很简单,从数据库查询,然后代码中利用相关Bitmap数据结构即可,这里还是推荐JavaEWAH。

如果一个场景需要算出男性,并且爱好是跑步,并且职业是程序员或者销售的人群,就可以这么算:

男性bitmap & 跑步bitmap &(程序员bitmap | 销售bitmap)

是不是很简单,比传统的一大段sql优雅而且效率高很多?

这时候我估计很多人提问了,单表查出集合构建bitmap不是也需要时间吗?而且也不快。。。

关于这个问题也是我做项目时候遇到的一个问题,后来的解决方案是,提前把标签的bitmap跑批生成到缓存里,这时候需要哪个bitmap时,直接从缓存里拿,速度提升巨大。

这时候还有个问题,这种跑批的Bitmap貌似失去了实时性,拿不到最新的标签?

其实一般的标签系统都不是实时的,也没必要实时,用户标签变动不会频繁,所以也是当天跑批昨天的标签,所以这么做,在业务上也是没有问题的。

作为一个优化,可以把每天的跑批在数据库和缓存里建议都存一份,这样提高数据的可靠性。

(3) 避坑

在生成bitmap时候,如果集合的流水id是乱序的,必须重新升序排序,如果是乱序,JavaEWAH在计算集合的时候会非常慢,这个也是我在实际开发中遇到的问题。

以上是我对于bitmap的介绍和应用场景,以及我在开发中的一些经验,希望对大家有帮助。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容