• 浅谈bitmap


    1. 定义:
        从字面意思上理解,bitmap翻译为位图,更准确地说应该是位的映射。bitmap一般应用于海量数据的处理,如查找、去重、排序。
        举个例子:40亿个int数字中,要找出只出现一次的数字集合。用普通的遍历查找的话,40亿int需要的内存空间是:40*10^8*4 = 16*10^9,即16GB的空间(ps:1GB大约是10亿字节)。对于一般计算机而言,内存大约2-8G,很明显无法存储16GB的数据。如果用存磁盘的方式分次加载,需要大量的I/O消耗,性能很差。这时候,就要使用bitmap了,其核心思想是:一个byte占8个bit,如果用一个bit表示一个int数字的值,即0表示这个数不存在,1表示整个数存在,那么一个byte就能表示8个int数字,一个int空间就能表示32个int数字。如下图所示:
                             
     这样的话,原本一个int数占32bit,现在只占1bit,即节省了32倍的空间。所以现在只需要16GB/32=512MB的内存空间,即只需要申请int bits[N/32 + 1]的空间就可存储数据,其中N表示这些数据中最大的数值数,此为2^32。此外,由于这些数字之间没有关联性,不需要同步处理,所以使用多线程的方式读取和加载数据可以实现更高的性能,时间复杂度大约是O(N/n),n表示线程数。
        注意:要说明的是,当有N个int数字用bitmap的方式存储时,如果N个int数字的数值都在0-N的范围内,那么使用bitmap可以节省32倍的内存空间;如果N个int数字的数值是0-MAXINT(即2^32)的范围,那么使用bitmap需要512MB的内存来存放所有的数,这样的话如果N小于1.25亿使用bitmap反倒多消耗了内存,只有N大于1.25亿才会节省内存,节省的内存倍数是:(4*N)B/512MB,如N为10亿int时,节省内存(4*10*10^8)B/512MB = 8。 
     
    2. 具体方法:
          接下来谈一下bitmap的实现方式。申请int bits[N/32+1]的空间后,一个int数如何定位到其索引位置及如何存放到bits数组中?如给定一个数33,我们知道应该将其放入bits[1]的第二个bit位置。
        (1)确定数组索引:使用数字除以32,即:num/32,也可写:num>>5
        (2)确定32位bit中的位置:使用数字对32取模,即:num%32,也可写为:num & 0x1F
        (3)数字存入bits中:bits[num/32] |= (1<<(num%32)),即:bits[num>>5] |= (1<<(num&0x1F))
        (4)数字从bits清除:bits[num/32] &= ~(1<<(num%32)),即:bits[num>>5] &= ~(1<<(num&0x1F))
        (5)判断数字是否在bits中:return ( bits[num/32] &= (1<<(num%32)) ) != 0 )
     
    3. 代码实现:
            此处C++代码默认N个int数字的数值范围在0-N中,即bitmap可以节省32倍的内存空间。
    #include <iostream>
    #include <vector>
    #include <cstring>
    using namespace std;
    class Bitmap
    {
    public:
        Bitmap(int N) :capacity(N) //使用bitmap节省空间
        {
            bits = new int[(capacity>>5)+1];
            for(int i=0; i<=(capacity>>5); ++i)
                bits[i] = 0;
        }
        ~Bitmap()
        {
            delete [] bits;
        }
        void Add(int num)    //添加数字num到bits中
        {
            bits[num>>5] |= (1<<(num&0x1F));
        }
        void Clear(int num)     //清除bits中的数字num
        {
            bits[num>>5] &= ~(1<<(num&0x1F));
        }
        string IsContain(int num)    //判断num是否在bits中
        {
            return (( bits[num>>5] & (1<<(num&0x1F)) ) != 0) ? "YES" : "NO";
        }
        void Sort()   //对bits中的数排序,时间复杂度O(capacity)
        {
            int cnt = capacity>>5;   //确定bits中的最大索引数
            while(cnt >= 0)
            {
                for(int i=31; i>=0; --i)
                {
                    if( (bits[cnt] & (1<<i)) != 0)
                        sortRes.push_back(cnt*32+i);
                }
                --cnt;
            }
        }
        void PrintSortRes()  //打印排序后的结果
        {
            cout<<"Sort :";
            for(auto i:sortRes)
                cout<<i<<" ";
            cout<<endl;
        }
    private:
        int capacity; //存储的int数据个数
        int *bits;    //指向存放数据的数组
        vector<int> sortRes;  //存放排序结果
    };
    int main()
    {
        Bitmap bm(100);
        bm.Add(4);
        bm.Add(37);
        bm.Add(99);
        cout<<"4 in bits? "<<bm.IsContain(4)<<endl;
        cout<<"37 in bits? "<<bm.IsContain(37)<<endl;
        cout<<"99 in bits? "<<bm.IsContain(99)<<endl;
        cout<<"89 in bits? "<<bm.IsContain(89)<<endl;
        bm.Clear(4);
        cout<<"4 in bits? "<<bm.IsContain(4)<<endl;
        vector<int> SortRes;
        bm.Sort();
        bm.PrintSortRes();
        return 0;
    }
     
    4. 扩展:
            如果要找出上亿整数中重复的数(多次添加的数)个数,可以用2-bitmap,即用2bit表示一个整数,00表示未出现,01表示出现一次,10表示出现多次。在遍历这些数时,如果对应位置是0则置为1,如果是1则置为2,如果是2则保持不变。遍历的同时统计对应位置为2的个数即为答案。
  • 相关阅读:
    Django关于StreamingHttpResponse与FileResponse响应文件或视频的下载请求
    APScheduler可能遇到的问题
    django中model聚合使用
    Java 递归判断迷宫问题是否有路
    direct path read/write (直接路径读/写)
    DRM 简介
    SQL Server2008表名中含“.”号处理方法
    Java学习之:JDK动态代理与CGLIB动态代理
    强大易用!新一代爬虫利器 Playwright
    为什么cudaMalloc()参数是二级指针
  • 原文地址:https://www.cnblogs.com/ladawn/p/8450235.html
Copyright © 2020-2023  润新知