位图是一种使用位数组存储数据的结构。每一位表示一个状态,通常用于快速判断某个值是否存在,或者用来表示布尔类型的集合。
判断某个用户 ID 是否存在。
插入一个用户 ID。
删除一个用户 ID。
计算两个用户 ID 集合的交集、并集和差集。
#include
#include
#include
using namespace std;
class Bitmap {
private:
vector bitmap; // 位图存储
size_t size; // 位图大小(支持的最大值)
public:
// 构造函数
Bitmap(size_t size) : size(size) {
bitmap.resize((size + 7) / 8, 0); // 每8个数占1字节
}
// 设置某个位为1(插入操作)
void set(int num) {
if (num >= size) return; // 越界检查
bitmap[num / 8] |= (1 << (num % 8));
}
// 重置某个位为0(删除操作)
void reset(int num) {
if (num >= size) return; // 越界检查
bitmap[num / 8] &= ~(1 << (num % 8));
}
// 检查某个位是否为1(查询操作)
bool get(int num) const {
if (num >= size) return false; // 越界检查
return bitmap[num / 8] & (1 << (num % 8));
}
// 求交集
Bitmap intersection(const Bitmap& other) const {
Bitmap result(size);
for (size_t i = 0; i < bitmap.size(); ++i) {
result.bitmap[i] = bitmap[i] & other.bitmap[i];
}
return result;
}
// 求并集
Bitmap unionSet(const Bitmap& other) const {
Bitmap result(size);
for (size_t i = 0; i < bitmap.size(); ++i) {
result.bitmap[i] = bitmap[i] | other.bitmap[i];
}
return result;
}
// 求差集
Bitmap difference(const Bitmap& other) const {
Bitmap result(size);
for (size_t i = 0; i < bitmap.size(); ++i) {
result.bitmap[i] = bitmap[i] & ~other.bitmap[i];
}
return result;
}
// 打印位图内容
void print() const {
for (size_t i = 0; i < size; ++i) {
if (get(i)) cout << i << " ";
}
cout << endl;
}
};
// 测试用例
int main() {
Bitmap bm1(100); // 位图1,范围为0到99
Bitmap bm2(100); // 位图2,范围为0到99
// 插入一些ID
bm1.set(10);
bm1.set(20);
bm1.set(30);
bm2.set(20);
bm2.set(30);
bm2.set(40);
cout << "Bitmap 1: ";
bm1.print(); // 输出:10 20 30
cout << "Bitmap 2: ";
bm2.print(); // 输出:20 30 40
// 求交集
cout << "Intersection: ";
Bitmap intersect = bm1.intersection(bm2);
intersect.print(); // 输出:20 30
// 求并集
cout << "Union: ";
Bitmap unionResult = bm1.unionSet(bm2);
unionResult.print(); // 输出:10 20 30 40
// 求差集
cout << "Difference (bm1 - bm2): ";
Bitmap difference = bm1.difference(bm2);
difference.print(); // 输出:10
return 0;
}
代码说明
set(num)
:将数字 num
对应的位置为1。reset(num)
:将数字 num
对应的位置清零。get(num)
:查询数字 num
是否存在。result.bitmap[i] = bitmap[i] & other.bitmap[i];
result.bitmap[i] = bitmap[i] | other.bitmap[i];
result.bitmap[i] = bitmap[i] & ~other.bitmap[i];
#include
#include
using namespace std;
class Bitmap {
private:
vector bitmap; // 位图存储
size_t size; // 位图支持的最大值
public:
Bitmap(size_t size) : size(size) {
bitmap.resize((size + 7) / 8, 0); // 每8个数占1字节
}
// 设置某个位为1
void set(int num) {
if (num >= size) return; // 超出范围检查
bitmap[num / 8] |= (1 << (num % 8));
}
// 检查某个位是否为1
bool get(int num) const {
if (num >= size) return false; // 超出范围检查
return bitmap[num / 8] & (1 << (num % 8));
}
};
// 使用位图实现数据去重
void removeDuplicates(const vector& input) {
const int MAX_VALUE = 1000000; // 数据范围:0到999999
Bitmap bitmap(MAX_VALUE);
vector uniqueNumbers;
for (int num : input) {
if (!bitmap.get(num)) { // 如果位未被设置,说明是新数据
uniqueNumbers.push_back(num);
bitmap.set(num); // 标记该数据已存在
}
}
// 输出去重后的数据
cout << "Unique numbers: ";
for (int num : uniqueNumbers) {
cout << num << " ";
}
cout << endl;
}
int main() {
// 测试数据
vector input = {10, 20, 30, 10, 20, 40, 50, 40, 30};
removeDuplicates(input); // 输出:10 20 30 40 50
return 0;
}
代码说明
Bitmap
类:
set(int num)
:将数字 num
对应的位设置为1,表示该数字已存在。get(int num)
:检查数字 num
是否已经存在。removeDuplicates
函数:
input
。#include
#include
using namespace std;
class Bitmap {
private:
vector bitmap; // 位图存储
size_t size; // 位图支持的最大位数
public:
Bitmap(size_t size) : size(size) {
bitmap.resize((size + 7) / 8, 0); // 每8个布尔状态占用1字节
}
// 设置某个位为1(开)
void setOn(int num) {
if (num >= size) return; // 越界检查
bitmap[num / 8] |= (1 << (num % 8));
}
// 设置某个位为0(关)
void setOff(int num) {
if (num >= size) return; // 越界检查
bitmap[num / 8] &= ~(1 << (num % 8));
}
// 查询某个位的状态
bool isOn(int num) const {
if (num >= size) return false; // 越界检查
return bitmap[num / 8] & (1 << (num % 8));
}
// 打印所有状态
void printStatus() const {
for (size_t i = 0; i < size; ++i) {
cout << "Device " << i << ": " << (isOn(i) ? "ON" : "OFF") << endl;
}
}
};
int main() {
const int NUM_DEVICES = 10000; // 管理10000个设备
Bitmap devices(NUM_DEVICES);
// 设置一些设备为开
devices.setOn(1);
devices.setOn(100);
devices.setOn(9999);
// 查询设备状态
cout << "Device 1: " << (devices.isOn(1) ? "ON" : "OFF") << endl; // 输出:ON
cout << "Device 2: " << (devices.isOn(2) ? "ON" : "OFF") << endl; // 输出:OFF
// 设置设备100为关
devices.setOff(100);
// 查询状态
cout << "Device 100: " << (devices.isOn(100) ? "ON" : "OFF") << endl; // 输出:OFF
// 打印前10个设备状态
for (int i = 0; i < 10; ++i) {
cout << "Device " << i << ": " << (devices.isOn(i) ? "ON" : "OFF") << endl;
}
return 0;
}
代码说明
setOn(int num)
:将设备编号对应的位设置为1(设备开)。setOff(int num)
:将设备编号对应的位清零(设备关)。isOn(int num)
:检查设备编号对应的位是否为1。布隆过滤器是一种基于位图的概率性数据结构,用于判断某个元素是否在集合中。它可能存在假阳性(误判元素存在),但不会有假阴性(漏判元素不存在)。
#include
#include
#include
using namespace std;
class BloomFilter {
private:
vector bitArray; // 位数组
vector> hashFuncs; // 哈希函数集合
size_t size;
public:
BloomFilter(size_t size, int numHashFuncs) : size(size), bitArray(size, false) {
for (int i = 0; i < numHashFuncs; ++i) {
hashFuncs.push_back(hash()); // 简单使用std::hash
}
}
void insert(int key) {
for (auto& hashFunc : hashFuncs) {
size_t index = hashFunc(key) % size;
bitArray[index] = true;
}
}
bool contains(int key) {
for (auto& hashFunc : hashFuncs) {
size_t index = hashFunc(key) % size;
if (!bitArray[index]) return false;
}
return true;
}
};
int main() {
BloomFilter bf(100, 3); // 位数组大小为100,使用3个哈希函数
bf.insert(10);
bf.insert(20);
cout << bf.contains(10) << endl; // 输出1
cout << bf.contains(30) << endl; // 输出0(一定不存在)
cout << bf.contains(20) << endl; // 输出1(可能存在)
return 0;
}
特性 | 位图 | 布隆过滤器 |
---|---|---|
存储效率 | 高 | 更高 |
查询效率 | 快速 | 快速 |
误判率 | 无误判 | 存在假阳性 |
数据删除 | 支持 | 不支持(需要Counting Bloom) |
典型应用 | 离散集合、计数 | 大规模数据集查询过滤 |
Redis 使用字符串类型存储 Bitmap。
sds
(Simple Dynamic String)。sds 源码文件:
sds.h
sds.c
Bitmap 数据存储: Bitmap 数据实际上以字符串形式存储在 robj
结构体中,定义在 object.c
:
struct redisObject {
unsigned type : 4; /* 数据类型(如 String、Hash) */
unsigned encoding : 4; /* 编码方式(如 RAW、INT 等) */
void *ptr; /* 实际数据的指针 */
};
对于 Bitmap,type
是字符串类型 (REDIS_STRING
),而 encoding
通常为 RAW
或 EMBSTR
,表示底层是动态字符串。
(1)SETBIT key offset value
设置指定位的值。
SETBIT key offset value
key
是存储 Bitmap 的 Redis 键。offset
是位的偏移量。value
是要设置的值(0 或 1)。offset
所属的字节位置:byte = offset / 8
。bit = offset % 8
。key
的值不足以存储该位,Redis 会自动扩展字符串长度。t_string.c
void setbitCommand(client *c) {
long long offset, byte, bit;
robj *o;
size_t bitoffset;
int byteval;
/* 获取 offset 参数并校验范围 */
if (getLongLongFromObjectOrReply(c, c->argv[2], &offset, NULL) != C_OK)
return;
if (offset < 0 || ((unsigned long long)offset >> 3) >= 512*1024*1024) {
addReplyError(c, "bit offset is not an integer or out of range");
return;
}
/* 获取 value 参数并校验 */
if (strcmp(c->argv[3]->ptr, "0") && strcmp(c->argv[3]->ptr, "1")) {
addReplyError(c, "bit value is not 0 or 1");
return;
}
/* 获取或创建字符串对象 */
o = lookupKeyWrite(c->db, c->argv[1]);
if (o == NULL) {
if (strcmp(c->argv[3]->ptr, "0") == 0) {
addReply(c, shared.czero);
return; /* 位是0,无需修改 */
}
o = createObject(OBJ_STRING, sdsnewlen(NULL, byte + 1));
dbAdd(c->db, c->argv[1], o);
}
/* 修改指定位 */
byte = offset / 8;
bit = 7 - (offset % 8);
byteval = ((unsigned char *)o->ptr)[byte];
byteval &= ~(1 << bit); /* 清零 */
byteval |= (bitval << bit); /* 置位 */
((unsigned char *)o->ptr)[byte] = byteval;
addReply(c, shared.cone);
}
(2)GETBIT key offset
获取指定位的值。
GETBIT key offset
offset
对应的字节和位位置。offset
超出字符串的长度,返回 0。void getbitCommand(client *c) {
robj *o;
long long offset;
unsigned char *bitmap;
size_t byte, bit;
int bitval = 0;
/* 获取 offset 参数 */
if (getLongLongFromObjectOrReply(c, c->argv[2], &offset, NULL) != C_OK)
return;
/* 计算字节和位位置 */
byte = offset / 8;
bit = 7 - (offset % 8);
/* 获取字符串对象 */
o = lookupKeyRead(c->db, c->argv[1]);
if (o != NULL && o->type == OBJ_STRING) {
bitmap = o->ptr;
if (byte < sdslen(bitmap)) {
bitval = bitmap[byte] & (1 << bit);
}
}
addReplyLongLong(c, bitval ? 1 : 0);
}
(3)BITCOUNT key [start end]
统计 Bitmap 中设置为 1 的位数。
BITCOUNT key [start end]
[start, end]
,只计算范围内的位。void bitcountCommand(client *c) {
robj *o;
long start, end;
size_t bitcount = 0;
/* 获取并校验范围 */
if (getRangeFromObjectOrReply(c, c->argv[2], c->argv[3], &start, &end) != C_OK)
return;
/* 获取字符串对象 */
o = lookupKeyRead(c->db, c->argv[1]);
if (o && o->type == OBJ_STRING) {
unsigned char *bitmap = o->ptr;
size_t len = sdslen(bitmap);
/* 遍历字节统计 1 的数量 */
for (size_t i = start; i <= end && i < len; i++) {
bitcount += __builtin_popcount(bitmap[i]);
}
}
addReplyLongLong(c, bitcount);
}
__builtin_popcount
快速统计 1 的数量。