实现一个 BitMap
楔子
本次来聊一聊 BitMap,这个在面试中经常会问到,在介绍它之前我们先来看一个问题。
假设当前你的服务有一千万个用户,每个用户都有一个整型ID,你要能够实时统计当前在线的人数,以及某个用户有没有登陆,这个时候你要怎么做呢?
首先我们想到用一个集合,来保存每个用户的 ID。当用户登陆时,就将 ID 插入到集合中;用户退出时,就将 ID 从集合中删除。所以集合中元素的数量就是当前的在线人数,而某个用户是否登陆就看其 ID 是否在集合中即可。
或者可以考虑使用数组,不是有一千万个用户吗?申请一个长度一千万的数组,初始时元素均为0,索引对应用户的ID。当用户登录时,就将该 ID 对应的数组元素设置为 1;用户退出时,将数组元素设置为 0。所以数组所有元素之和就是当前的在线人数,而某个用户是否登陆就看其 ID 对应的数组元素是否为 1 即可。
这是非常正确的思路,实现起来也非常方便,但是比较难缠的面试官可能会问你还有没有其它的做法。注意:并不是说这种做法不对,只是想顺着这个话题拓展一下思维。数组实现的话会耗费内存,如果活跃的用户非常多,那么该集合也会很耗费内存。这个时候面试官可能考察的就是 BitMap,下面就来介绍一下。
什么是 BitMap?
我们上面介绍了通过数组去存储,BitMap 也是类似的做法,只不过 BitMap 是用一个位表示一个用户。举个栗子,假设我们想表达 0 到 30,那么按照之前的逻辑,数组里面要有 31 个元素。但是采用 BitMap 的话,数组只需要一个元素即可,如果采用 long 的话,那么有 64 个位,足以表示这 31 个数。假设 ID 为 20 的用户登录了,我们就把该整数的第 21 个位设置为 1 即可。
那如果要表示 65 呢?一个 long 有 64 个位,最高能表示 63, 那么就再来一个整数,将第二个整数的第 3 个位设置为 1 即可。
所以原来数组中一个元素只能表示一个用户,现在一个元素可以表示 64 个用户,内存占用变成了原来的 \({\frac 1 {64}}\)。
实现 BitMap
下面我们就来使用 Python 实现一下 BitMap。
class BitMap:
def __init__(self, max_count: int):
"""
:param max_count: 最多容纳多少个用户
"""
# 申请指定容量的列表,每个元素用 64 字节
self.__array = [0] * ((max_count >> 6) + 1)
def put_user(self, user_id: int):
"""
将用户 ID 加入的 BitMap 中
:param user_id:
:return:
"""
# 计算出所在的索引
# 然后找到指定的位,将该位更新为 1
self.__array[user_id >> 6] |= 1 << (user_id & 63)
def get_user(self, user_id):
"""
根据 user_id 获取用户,或者说判断用户是否登陆
:param user_id:
:return:
"""
# 计算出所在的索引,判断指定的位是否为 1
return self.__array[user_id >> 6] >> (user_id & 63) & 1 == 1
def delete_user(self, user_id):
"""
根据 user_id 删除用户
:param user_id:
:return:
"""
self.__array[user_id >> 6] &= ~(1 << (user_id & 63))
def online_user_count(self):
"""
返回当前在线用户总数
也就是统计每一个元素中 1 的位数,然后相加
:return:
"""
count = 0
for item in self.__array:
count += bin(item).count("1")
return count
bm = BitMap(1000_0000)
print(bm.get_user(213456)) # False
bm.put_user(213456)
print(bm.get_user(213456)) # True
print(bm.online_user_count()) # 1
bm.delete_user(213456)
print(bm.get_user(213456)) # False
print(bm.online_user_count()) # 0
总的来说还算比较简单的。
如果觉得文章对您有所帮助,可以请囊中羞涩的作者喝杯柠檬水,万分感谢,愿每一个来到这里的人都生活愉快,幸福美满。
微信赞赏
支付宝赞赏