位图与布隆过滤器

一、问题引入

先来思考这样一个问题:假如给你20亿个数字,范围大小是 1- 20亿,需要你把这些数字存储起来,然后再随机给定一个数字,判断其是否存在这20亿个数字中, 你怎么解决。
首先如果不考虑其个数多少的话,我们首先想到的可能就是使用一个set集合,用来存储数字集合,然后直接判断数字是否存在,因为其底层使用了hash的数据结构,判断给定数字是否存在也是O(1) 的时间复杂度。但是当这个数字来到10亿,乃至更大的时候,如果还是用set集合,可能会极大得浪费我们的存储空间,并且可能会引发OOM(我们可以大概估算一下,java中,一个int类型的数字是4个字节,取值范围:-2147483648~+2147483647。那么这20亿个数据如果我们用int类型表示,那么占用内存就是 4 * 20亿 = 80亿字节,约等于8个G)。这个时候,我们需要怎么解决这个问题呢?
我们主要解决的是占用内存过大的这个问题,那我们想,有没有比字节更小的单位呢,没错了,就是bit 位。 我们知道,计算机中,最原始都是使用二进制表示,int类型有32bit位,如果我们用每个bit位表示一个数字,那么一个int类型就能够存储32个数字,而且题干中刚好只要求我们能够判断目标数字是否存在,那我们只需要对应的bit位上存储 1 或者 0,用来表示存在或者不存在。这么算下来,原本 约8个G的存储 / 32,也就是200多M,就可以解决这个问题。

二、思路归纳

为了方便计算,我们暂且将 数字缩小化便于理解。
比如给定的数字集合,最大是130。现在我们向集合中添加128,129这两个数字,具体应该怎么添加呢?
1、首先要确定这个集合怎么表示,我们可以使用int类型的数组(当然你也可以使用Long类型的,这里只是举例),因为int类型占用4个字节,32位,所以一个int可以表示32个数字,也就是int[0] 可以表示0-31,int[1]表示32-63,依次类推。
2、有了数组,那么我们就要确定给定的数字位于数组的下标位置。我们可以用 (130 / 32 )+1 = 5 来表示下标位置。为什么数组长度需要+1呢,因为4个长度的int数组只能表示0-127,所以多出来的几个数字,只能数组长度再加一。
3、有了下标,还需要找到数组下标中的bit位置。128 % 32 = 0 来表示在第几个bit位。
向int数组中添加128,


128

向int数组中添加129,


128,129

有了如上添加数字将对应bit位置置为1的逻辑,接下来给定一个数字,判断这个数字是否存在于集合内,就只要判断该数字对应的big位上 的值是否等于1 即可。
比如给定100,首先找到数组下标 index = 100 / 32 = 3, num = 100 % 32 = 4, 即只要判断bigmap[2]的第四个bit位上是否等于1,等于1,说明存在,不等于1,则不存在。

三:布隆过滤器

所谓位图,就是使用位这个最小的存储单元来储存,针对于Integer4个字节,根据位的高低来存储不同大小的数字。正常来说,20亿个正整型数字,数组来存,需要20亿的长度,但是如果用位来存,一个Integer占32位,也就是数组中的一个Integer可以表示32个数字,那么就整整缩小了32倍的数组长度。
问题一:
我们想,现在只是针对数字,如果我们把范围扩大到其他类型,如果是字符串呢,那么我们就需要先对目标进行哈希运算,然后再计算其数组下标。
问题二:
同时我们的哈希表(数组)长度总是有限的,如果产生哈希冲突,那么最后判断哈希表中的值,就会不准确。那么怎么解决呢?通过设置多个哈希函数,同时将多个位上的值置为1。当判断一个key所对应的值存不存在时,通过多个哈希函数得到的数组下标所对应的值都为1时,才能确定这个key是存在的,否则不存在。这样当然也可能会存在误判,不过我们可以根据业务场景的容忍度,通过设置哈希表的大小和哈希函数的个数来调整误判的概率。redis中的布隆过滤器也就是这么做的。

java的位图实现:

/**
 * @author zhangxiaohu
 * @descript int整数位图
 * @date 2022-10-12
 */
public class BitMap {

    private int[] bitMap;
    /**
     * java中int 占用4个字节,共32位
     * 如果最大数值是 128,那么bigMap 数组的长度就是 4(占用128位),如果用普通的方式存储,最大值是128,那么我们的 数组长度就是128,相比之下,内存空间减少了32倍
     * @param range
     */
    public BitMap(int range) {
        /**
         * range >> 5 = range / 32
         * 这里为什么要 + 1呢,这是因为,比如最大值是128,数组长度是4,最多只能存储到 0 - 127,所以存储128数组长度要再+1
         * 数组长度+1之后,所以range = 128,但是我们可以表示的最大值应该是  128 + 31 = 159
         */
        bitMap = new int[(range >> 5) + 1];
    }

    public void set(Integer number) {
        /**
         * number >> 5 = number / 32
         * 假如number  =  128
         * index = 4  表示在数据的最后一个位置,也就是bitMap[3]
         * num = 0 表示在bitMap[3]上的第0位(bitMap[3] 上有32位),也就是: 00000000 00000000 00000000 00000001
         */
        int index = number >> 5;
        int num = number % 32;
        /**
         * 1 << num = 1 * 2的num次幂,保证相应位置上的值无论为0还是1,都变成1
         * 这里为什么要用 | 运算呢 ,  | 或运算:有一个为1,值就为1
         * 是因为多次给每个bitMap位置上的32个元素赋值为1时,这32个值不相互影响
         * 比如,第一次number=128,bitMap[3] = 00000000 00000000 00000000 00000001
         * 第二次number = 130,  bitMap[3] 不应该 =  00000000 00000000 00000000 00000100,而是应该 = 00000000 00000000 00000000 00000101
         * 所以需要每次赋值时进行或运算
         */
        bitMap[index] = bitMap[index] | 1 << num;
    }

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

推荐阅读更多精彩内容