在之前的介绍过的哈希表中,如果要在表中存放一个整数,此时就要申请一个整型的内存来存放它,一个整型数据在32位或64位平台下都占4个字节。如果现在需要存储的数据非常多,比如说40亿个不重复的数据,就需要160亿个字节来存储,1GB的内存表示10亿个字节,此时就需要16GB的内存来存放这些数据,而我们普通的电脑内存一般都是4G的内存,这显然是不够大的。我们知道,内存中的最小单位是比特位。如果能用一个比特位来存放一个整型,只需要0.5GB的内存。
一个比特位可以被设置为0或1,如果要表示40亿个数据,可以申请0.5GB的内存。如果要存放的数据为10,就将第10个比特位设置为1。如果要查找的数据为100,就查看第100个比特位处的状态,如果为1说明,100存在于这堆数据中,如果为0说明不存在。像这样用一个比特位来表示一个数是否存在,这种结构就是位图。
位图最大的优点就是节省空间;但是同样它的最大缺点就是只能表示数据是否存在,而不能用来存放数据。
下面我们就来实现一下位图的基本操作:
1. 结构定义
其中capacity表示能容纳的元素有多少(单位为比特位),这个要在初始化的时候定长,然后申请空间。若之后觉得空间太小,可以再扩容。
#pragma once #include <stdio.h> #include <stdint.h>//uint64_t #include <stdlib.h> #include <string.h> #define SHOW_NAME printf("\n========================%s=======================\n", __FUNCTION__); typedef uint64_t BitmapType;//uint64_t类型可跨平台使用,占8字节 typedef struct Bitmap { BitmapType* data; uint64_t capacity; }Bitmap;
2. 初始化
//1.初始化 void BitmapInit(Bitmap* bm, uint64_t capacity)//初始化 { if(bm == NULL)//非法输入 return; bm->capacity = capacity;//表示位图能保存的最大数(bit数) uint64_t size = bm->capacity/(sizeof(bm->data[0])*8) + 1;//要申请的元素个数 bm->data = (BitmapType*)malloc(sizeof(BitmapType)*size); memset(bm->data, 0, sizeof(BitmapType)*size);//初始化数据 return; }
3. 销毁
//2.销毁 void BitmapDestroy(Bitmap* bm) { if(bm == NULL) return; bm->capacity = 0; free(bm->data); return; }
4. 测试函数——测试某个比特位是0还是1
//3.测试函数—输出某一位是0还是 void GetOffset(uint64_t index, uint64_t* n, uint64_t* offset)//确定要测试的是在第几个元素的第几个比特位 {//n输出元素下标,offset输出比特位置 *n = index / (sizeof(BitmapType)*8); *offset = index % (sizeof(BitmapType)*8); return; } int BitmapTest(Bitmap* bm, uint64_t index)//该bit位上是1返回1,是0返回0 {//index表示是数组中的第多少位bit if(bm == NULL || index >= bm->capacity)//非法输入 return 0; uint64_t n,offset; GetOffset(index, &n, &offset); uint64_t ret = bm->data[n] & (0x1ul << offset);//这里使用0x1ul是为了防止因为类型问题导致移位得到的数据出错 return ret > 0 ? 1 : 0; }
5. 设置某个比特位为1
//4.设置某个比特位为1 void BitmapSet(Bitmap* bm, uint64_t index) { if(bm == NULL || index >= bm->capacity)//非法输入 return; uint64_t n,offset; GetOffset(index, &n, &offset); bm->data[n] |= (0x1ul << offset);//或1 一定为1 return; }
6. 设置某个比特位为0
//5.设置某个比特位为0 void BitmapUnSet(Bitmap* bm, uint64_t index) { if(bm == NULL || index >= bm->capacity)//非法输入 return; uint64_t n,offset; GetOffset(index, &n, &offset); bm->data[n] &= ~(0x1ul << offset);//与0 一定为0 return; }
7. 设置所有比特位为1
//6.设置所有比特位为1 void BitmapFill(Bitmap* bm) { if(bm == NULL)//非法输入 return; uint64_t size = bm->capacity/(sizeof(bm->data[0])*8) + 1; memset(bm->data, 0xff, sizeof(BitmapType)*size); return; }
8. 设置所有比特位为0
//7.设置所有比特位为0 void BitmapEmpty(Bitmap* bm) { if(bm == NULL)//非法输入 return; uint64_t size = bm->capacity/(sizeof(bm->data[0])*8) + 1; memset(bm->data, 0x0, sizeof(BitmapType)*size); return; }
以下是测试函数:
void Test() { SHOW_NAME; Bitmap bm; BitmapInit(&bm, 100); printf("capacity: expected is 100, actual is %d\n", bm.capacity); printf("expected is 0, actual is %lu\n", bm.data[0]);//uint64_t类型用%lu输出 BitmapSet(&bm, 4); printf("expected is 1, actual is %d\n", BitmapTest(&bm, 4)); BitmapUnSet(&bm, 7); printf("expected is 0, actual is %d\n", BitmapTest(&bm, 7)); BitmapFill(&bm); printf("expected is 1, actual is %d\n", BitmapTest(&bm, 31)); BitmapEmpty(&bm); printf("expected is 0, actual is %d\n", BitmapTest(&bm, 27)); BitmapDestroy(&bm); printf("expected is 0, actual is %d\n", bm.capacity); }