Bitmap的原理和应用

1,357次阅读
没有评论

面试中经常会问到类似问题,看上去很简单,就是一个排序而已,但是你好好想想大部分排序算法都需要把数据放到内存里面操作,这10亿个数字得占用多少内存?好吧,你可以使用外部排序算法,在磁盘上完成排序!当然这些传统算法肯定是可以解决的,不过这里有一个更好的方案,采用bitmap排序,介绍如下:

bitmap是什么? 大家都知道在计算机中一个字节(byte) = 8位(bit), 这里的bit就是位,数据的最小表示单位,map一般是表示地图或者映射,加一起叫作位图?貌似不太形象

简单回顾一下二进制的一些知识:

1byte = 8bit

一个bit有2种状态,0 或者 1

所以1个byte可以表示0000 0000 -> 1111 1111, 也就是十进制的 0 到 255

其中十进制和二进制对应关系如下:

0 ---------> 0000 0000
 1 ---------> 0000 0001
 2 ---------> 0000 0010
 3 ---------> 0000 0011
 4 ---------> 0000 0100
 5 ---------> 0000 0101
 6 ---------> 0000 0110
 7 ---------> 0000 0111
 8 ---------> 0000 1000
 9 ---------> 0000 1001
10 ---------> 0000 1010
11 ---------> 0000 1011
12 ---------> 0000 1100
13 ---------> 0000 1101
14 ---------> 0000 1110
15 ---------> 0000 1111
.......................
.......................
255...........1111 1111

在大部分编程语言里面,int类型一般的都是占4个byte,也是32位,甭管你这个数字是1 或者是 21亿你都得占32位,所以如果你现在有10亿数字需要存放在内存里面,需要多少内存呢?

1000000000 * 4 / 1024 / 1024 = 3800MB,大概需要3800MB内存,这里计算出的数值只适合C,在PHP里面,一个整型变量占用的实际空间远远大于4byte,是好几倍!

为了解决这个问题,bitmap采用了一种映射机制,举个例子,假如有 1,3, 7,2, 5 这5个数字需要存放,正常情况下你需要5*4=20byte,但bitmap只需要1byte,它是咋做到呢?

假设下面是1byte,首先将所有位置为0:

0 0 0 0 0 0 0

从第一个0开始数数,把对应数字的位置置为1,比如说第一个1那就是第2个位置置为1,第二个3就是把第4个位置置为1,依此论推…

1 => 0 1 0 0 0 0 0 0
3 => 0 0 0 1 0 0 0 0
7 => 0 0 0 0 0 0 0 1
2 => 0 0 1 0 0 0 0 0
5 => 0 0 0 0 0 1 0 0

叠加起来最终的串就是:

0 1 1 1 0 1 0 1

其实最终的数字和二进制没有什么关系,纯粹是数数,这个串就可以代表最大到7的数字,然后我们就开始数数,从0开始:

比如第1个位置是1,那就记个1
比如第2个位置是1,那就记个2
比如第3个位置是1,那就记个3
比如第5个位置是1,那就记个5
比如第7个位置是1,那就记个7

结果就是 1 2 3 5 7,不仅仅排序了,而且还去重了!如果按照这种转换机制,1个int类型,32位的话,可以表示0-31之间的数字!

如果你们要表示最大1万的数,那就需要1万个位的串,但是编程语言并没有这样的数据类型,但是可以用数组去模拟

举个例子:一个整型是32位,也就说我们大概需要314个数组元素来表示这个串

数组第1个元素 00 – 31

数组第2个元素 32 – 63

数组第3个元素 64 – 95

数组第4个元素 96 – 127

… …

提到这个算法的好处,最大的好处就是节省内存,节省了好几十倍,适合处理大量数据,除了快速排序,还可以做快速去重,快速查询是否存在,还有一个比较好听的应用 Bloom Filter(布隆过滤器):

Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为1(1≤i≤k)。注:如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。 Bloom Filter 在判断y是否属于这个集合时,对y应用k次哈希函数,若所有hi(y)的位置都是1(1≤i≤k),就认为y是集合中的元素,否则就认为y不是集合中的元素。

代码

下面是这个算法的一些演示代码:

<?php

namespace App\bitmap;

class Bitmap
{
const MAX = 10000;
const SHIFT = 5;
const MASK = 0x1F;
const DIGITS = 32;

private $bits = [];

public function __construct()
{
$len = 1 + self::MAX / self::DIGITS;
for ($i = 0; $i < $len; $i++) {
$this->bits[$i] = 0;
}
}

public function set(int $n)
{
$this->bits[$n >> self::SHIFT] |= (1 << ($n & self::MASK));
}

public function clear(int $n)
{
$this->bits[$n >> self::SHIFT] &= (~(1 << ($n & self::MASK)));
}

public function test(int $n)
{
return $this->bits[$n >> self::SHIFT] & (1 << ($n & self::MASK));
}
}

$bitmap = new Bitmap();

for ($i = 0; $i < Bitmap::MAX; $i++) {
$bitmap->clear($i);
}

$exampleData = [1, 23, 34, 5454, 677, 834, 123, 355, 6784, 2345, 98, 9782, 432, 2342, 872, 732, 2334];
foreach ($exampleData as $item) {
$bitmap->set($item);
}

for ($i = 1; $i <= Bitmap::MAX; $i++) {
if ($bitmap->test($i)) {
printf("%d ", $i);
}
}

此算法的实现是参考一个C语言版本的,简单解析一下:

第一步,是初始化一个数组,这个数组的长度是根据最大的元素的值来的,比如说你要存一个最大10000的数,由于每个元素最多32位,所以需要大概314个数组。

第二步,初始化数组中的每个元素,把每个位都置成0,在PHP里面其实不需要,但是C里面是必须的,使用了clear这个函数。

SHIFT 是表示位移的位数,之所以是5是因为2的5次方是32,简单的说 $n >> self::SHIFT 这个操作是为了找到当前元素在哪个数组!

MASK 是表示掩码,0x1F是 十进制的31,1 << ($n & self::MASK) 这个操作是计算出当前元素在对应数组里面位置!

举个例子,当前元素是100,按照算法,是在第3个数组里面,下标为4的位置,大家仔细推敲一下,结果确实是这样!只不过这里运用了不少位操作,所以理解起来可能会麻烦一点。

REDIS bitmap相关应用

自己造轮子太累,redis提供了类似的命令,最大可以存放2的32次方,即21亿多的数字,主要有以下几个:SETBIT, GETBIT, BITCOUNT, BITOP, BITPOS,BITFIELD,

主要用来做活跃用户在线状态、活跃用户统计、用户签到等场景,特别适合大量用户,几千万上亿级别,当然你用传统数据库也能做,但是redis做起来更简单,更节省空间!

下面举一个用户签到的功能设计案例:

很多App都有一个签到功能,比如说连续签到7天或者30天给一些奖励,需求十分简单!

作为后端,我们需要提供一个签到接口,然后记录用户签到的信息,比如用户uid,签到时间!

如果使用传统关系型数据库,我们可能需要建一张签到表,大概有id、uid、createdTime等几个字段,当用户签到的时候新增一条记录就行!这种做法肯定是没问题的,但是如果网站每天有千万用户签到,那么这张表每天都会有千万条记录产生,数据的存储是问题!分库分表必不可少!

假如使用redis的bit操作,我们可以使用setbit,SETBIT key offset value 对指定的key的value的指定偏移(offset)的位置1或0, 其中key我们可以设置为当天的年月日,offset是用户uid(这里暂时只考虑uid是纯数字的情况),value的话1表示已签到。比如说用户uid位12500的用户在20190501签到了,我们可以执行SETBIT 20190501 12500 1,其它用户依此论推!

如果我们需要查询用户某天是否签到,只需要使用GETBIT 20190501 12500,返回1表示已签到,0未签到。

如果需要查询某天有多少人签到,可以使用BITCOUNT 20190501

如果要统计连续7天签到的总人数的话可以使用bitop命令,比如bitop AND 7_dasy_sign 20190501 20190502 20190503 ... 20190507

理论上讲,setbit的大小在0到2的32次方(最大使用512M内存)之间,即0~4294967296之间,也就说最多可以存储42亿用户的签到情况。和数据库相比,这种方式查询的效率非常高,并不会因为数据大而变慢,而且比较节省内存,操作上也不是太复杂

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)

文心AIGC

2023 年 9 月
 123
45678910
11121314151617
18192021222324
252627282930  
文心AIGC
文心AIGC
人工智能ChatGPT,AIGC指利用人工智能技术来生成内容,其中包括文字、语音、代码、图像、视频、机器人动作等等。被认为是继PGC、UGC之后的新型内容创作方式。AIGC作为元宇宙的新方向,近几年迭代速度呈现指数级爆发,谷歌、Meta、百度等平台型巨头持续布局
文章搜索
热门文章
潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026 Jay 2025-12-22 09...
“昆山杯”第二十七届清华大学创业大赛决赛举行

“昆山杯”第二十七届清华大学创业大赛决赛举行

“昆山杯”第二十七届清华大学创业大赛决赛举行 一水 2025-12-22 17:04:24 来源:量子位 本届...
MiniMax海螺视频团队首次开源:Tokenizer也具备明确的Scaling Law

MiniMax海螺视频团队首次开源:Tokenizer也具备明确的Scaling Law

MiniMax海螺视频团队首次开源:Tokenizer也具备明确的Scaling Law 一水 2025-12...
天下苦SaaS已久,企业级AI得靠「结果」说话

天下苦SaaS已久,企业级AI得靠「结果」说话

天下苦SaaS已久,企业级AI得靠「结果」说话 Jay 2025-12-22 13:46:04 来源:量子位 ...
最新评论
ufabet ufabet มีเกมให้เลือกเล่นมากมาย: เกมเดิมพันหลากหลาย ครบทุกค่ายดัง
tornado crypto mixer tornado crypto mixer Discover the power of privacy with TornadoCash! Learn how this decentralized mixer ensures your transactions remain confidential.
ดูบอลสด ดูบอลสด Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
Obrazy Sztuka Nowoczesna Obrazy Sztuka Nowoczesna Thank you for this wonderful contribution to the topic. Your ability to explain complex ideas simply is admirable.
ufabet ufabet Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
ufabet ufabet You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
ufabet ufabet Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
热评文章
摩尔线程的野心,不藏了

摩尔线程的野心,不藏了

摩尔线程的野心,不藏了 量子位的朋友们 2025-12-22 10:11:58 来源:量子位 上市后的仅15天...
摩尔线程的野心,不藏了

摩尔线程的野心,不藏了

摩尔线程的野心,不藏了 量子位的朋友们 2025-12-22 10:11:58 来源:量子位 上市后的仅15天...
AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身

AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身

AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身 量子位的朋友们 2025...
AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身

AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身

AI体育教练来了!中国团队打造SportsGPT,完成从数值评估到专业指导的智能转身 量子位的朋友们 2025...
真正面向大模型的AI Infra,必须同时懂模型、系统、产业|商汤大装置宣善明@MEET2026

真正面向大模型的AI Infra,必须同时懂模型、系统、产业|商汤大装置宣善明@MEET2026

真正面向大模型的AI Infra,必须同时懂模型、系统、产业|商汤大装置宣善明@MEET2026 量子位的朋友...