bitmap,bitSet和布隆过滤器讲解

本文探讨了Bitmap算法和布隆过滤器在大数据处理中的应用。Bitmap算法通过位操作实现高效存储和查询,大幅节省内存空间。布隆过滤器则提供了一种快速判断元素是否可能存在于集合中的方法,适用于大量数据的预筛选。两者均利用位数组减少存储成本,但各有优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存

在Java中,int占4字节, 1字节=8位(1 byte = 8 bit)

如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)= 7.47 G

如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)= 0.233G

bitmap算法:

所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。

那么,问题来了,如何表示一个数呢?刚才说了,每一位表示一个数,0表示不存在,1表示存在,这正符合二进制。

这样我们可以很容易表示{1,2,4,6}这几个数:

计算机内存分配的最小单位是字节,也就是8位,那如果要表示{12,13,15}怎么办呢?

当然是在另一个8位上表示了:

这样的话,好像变成一个二维数组了

1个int占32位,那么我们只需要申请一个int数组长度为 int tmp[1+N/32] 即可存储,其中N表示要存储的这些数中的最大值,于是乎:

tmp[0]:可以表示0~31

tmp[1]:可以表示32~63

tmp[2]:可以表示64~95

。。。

如此一来,给定任意整数M,那么M/32就得到下标,M%32就知道它在此下标的哪个位置

这里有个问题,我们怎么把一个数放进去呢?例如,想把5这个数字放进去,怎么做呢?

首先,5/32=0,5%32=5,也是说它应该在tmp[0]的第5个位置,那我们把1向左移动5位,然后按位或,相当于b[0] = b[0] | (1<<5)

假设我们要6移除,该怎么做呢?b[0] = b[0] & (~(1<<(6%8)))

查找

前面我们也说了,每一位代表一个数字,1表示有(或者说存在),0表示无(或者说不存在)。通过把该为置为1或者0来达到添加和清除的小伙,那么判断一个数存不存在就是判断该数所在的位是0还是1

假设,我们想知道3在不在,那么只需判断 b[0] & (1<<3) 如果这个值是0,则不存在,非0,就表示存在。

public class BitMap {
    public static void main(String[] args) {
        BitMap bit = new BitMap(1000000L);
        bit.setN(10000);
        System.out.println(bit.isExist(10000));
        bit.clear(10000);
        System.out.println(bit.isExist(10000));
    }
    private long length;
    private static int[] bitsMap;
    public BitMap(long length)
    {
        this.length = length;
        /**
         * 根据长度算出,所需数组大小
         * 当 length % 32 = 0 时大小等于
         * = length / 32
         * 当 length % 32 > 0 时大小等于
         * = length / 32 + l
         */
        bitsMap = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
        //数组的length属性时int(long型数组也是int),所以理论最大应该时Integer.MAX_VALUE数组的极限大小受jvm设置的最大内存限制
    }
    public void setN(long n) {
        if (n < 0 || n > length) {
            throw new IllegalArgumentException("length value " + n + " is  illegal!");
        }
        // 求出该n所在bitMap的下标,等价于 "n / 32"
        int index = (int) n / 32;
        // 求出该值的偏移量(求余),等价于"n % 32"
        int offset = (int) n & 31;
        bitsMap[index] |= 1 << offset;
        printNum(n);
    }
    /**
     * 获取值N是否存在
     */
    public boolean isExist(long n) {
        if (n < 0 || n > length) {
            throw new IllegalArgumentException("length value illegal!");
        }
        int index = (int) n >> 5;
        int offset = (int) n & 31;
        int bits = bitsMap[index];
        //printNum(n);
        return (bits & (1 << offset)) != 0;
    }

    public void clear(long n)
    {
        if (n < 0 || n > length) {
            throw new IllegalArgumentException("length value illegal!");
        }
        int index = (int) n >> 5;
        int offset = (int) n & 31;
        bitsMap[index] &= (~(1 << offset));
        printNum(n);
    }
    public void printNum(long n)
    {
        if (n < 0 || n > length) {
            throw new IllegalArgumentException("length value illegal!");
        }
        int index = (int) n >> 5;
        int offset = (int) n & 31;
        System.out.println("index " + index + " offset " + offset);
        int tem = bitsMap[index];
        int cur = 0;
        while(cur <= 31)
        {
            cur++;
            String split = cur % 4 == 0 ? "  " : " ";
            System.out.print(tem % 2 + split);
            tem /= 2;
        }
    }
}

 

优点 :

  • 运算效率高,不需要进行比较和移位;
  • 占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M

缺点 :

  • 所有的数据不能重复。即不可对重复的数据进行排序和查找。
  • 只有当数据比较密集时才有优势

bitSet:

在bitset中就是使用着bitmap方式,将数据存储在long数组中,主要的操作就是位操作。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet修改另一个 BitSet的内容。 线程不安全。默认情况下,set 中所有位的初始值0,所以get的时候都是false。 

BitSet的size方法:返回此 BitSet 表示位值时实际使用空间的位数,值是64的整数倍

length方法:返回此 BitSet 的“逻辑大小”:BitSet 中最高设置位的索引加 1。

案例:

有1万个随机数,随机数的范围在1- 1万之间。现在要求写出一种算法,将0到10001亿之间没有在随机数取出来,为了方便测试才设计的1万? 

    public static void main(String [] args)
    {
        int count = 10000;
        Random random = new Random();
        Set<Integer> ran = new HashSet<>();
        while (ran.size() < count)
        {
            ran.add(random.nextInt(count) + 1);
        }
        BitSet bitSet = new BitSet(10000);
        ran.forEach(bitSet::set);
        IntStream.range(0, 10001).filter(e -> !bitSet.get(e)).forEach(System.out::println);//0
    }

布隆过滤器:

当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1 。检索时,只要看看这些点是不是都是1就知道元素是否在集合中;如果这些点有任何一个 0,则被检元素一定不在;如果都是1,则被检元素很可能在(之所以说“可能”是误差的存在)。误差举例:当bitmap的数组长度很小,而数据又很多的情况下,有可能判断元素在集合中,但是元素不在,原因:这个元素所有索引位置上面的1都是别的元素设置的,这就导致一定的误判几率。

BloomFilter 流程

  1. 首先需要 k 个 hash 函数,每个函数可以把 key 散列成为 1 个整数;
  2. 初始化时,需要一个长度为 n 比特的数组,每个比特位初始化为 0;
  3. 某个 key 加入集合时,用 k 个 hash 函数计算出 k 个散列值,并把数组中对应的比特位置为 1;
  4. 判断某个 key 是否在集合时,用 k 个 hash 函数计算出 k 个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

public class BloomFilter {
    private static final int DEFAULT_SIZE = 2 << 24;
    private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 };
    private BitSet bits = new BitSet(DEFAULT_SIZE);
    private SimpleHash[] func = new SimpleHash[seeds.length];
    // 内部类,simpleHash
    public static class SimpleHash {
        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        public int hash(String value) {
            int result = 0;
            int len = value.length();
            for (int i = 0; i < len; i++) {
                result = seed * result + value.charAt(i);
            }
            return (cap - 1) & result;
        }
    }

    public BloomFilter() {
        for (int i = 0; i < seeds.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }

    public void add(String value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    public boolean contains(String value) {
        if (value == null) {
            return false;
        }
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(value));
        }
        return ret;
    }
    public static void main(String [] args)
    {
        BloomFilter bf = new BloomFilter();
        List<String> strs = new ArrayList<String>()
        {{
            add("123456");
            add("hello word");
            add("transDocId");
            add("123456");
            add("transDocId");
            add("hello word");
            add("test");
        }};

        strs.forEach(e -> {
            boolean bl = bf.contains(e);
            if(bl){
                System.out.println(e);// 123456 transDocId hello word
            }else{
                bf.add(e);
            }
        });
    }
}

 

<think>好的,用户想了解C++中位图布隆过滤器的相关知识及总结,我需要结合提供的引用资料来组织回答。首先,我应该先明确位图布隆过滤器的基本概念,然后讨论它们的实现应用,最后总结它们的优缺点应用场景。 首先看位图部分。根据引用[3],位图用于处理海量数据的存在性判断,比如40亿个整数去重。位图通过每个bit位表示一个数据是否存在,节省空间。需要提到位图的数据结构设计,比如用vector<bool>或直接操作比特位。实现部分可能需要给出C++的代码示例,比如设置某位为1,清除为0,检查存在性。应用方面包括快速查找、去重、排序等,引用[2][3]都有提到这些应用。 然后是布隆过滤器。根据引用[4],布隆过滤器是概率型数据结构,用于判断元素可能存在或一定不存在。它使用多个哈希函数映射到不同的位,引用[3][4]都提到了这一点。实现部分需要说明插入查找的过程,以及为什么不能删除,或者如何支持删除(可能需要引用计数)。应用方面,引用[5]提到了缓存穿透的解决方案,防止数据库被无效查询压垮。此外,分布式系统爬虫中的URL去重也是常见应用。 需要注意布隆过滤器的优缺点,比如空间效率高但存在误判率,无法删除元素等。引用[4][5]都有相关描述。可能需要比较位图布隆过滤器的适用场景,比如位图适合精确判断且数据范围小的情况,而布隆过滤器适合大数据量且允许一定误判的情况。 代码实现部分,位图可以用bitset或手动操作位,布隆过滤器则需要多个哈希函数位图的组合。需要确保示例代码正确,比如展示如何设置位,检查存在性,以及布隆过滤器的插入查询步骤。 最后总结部分,需要将位图布隆过滤器的核心点对比,强调它们在海量数据处理中的重要性,并结合引用中的内容,如引用[1]提到的结合位图布隆过滤器的方案,可能可以提到这种混合结构在优化空间查询速度上的优势。 现在需要检查是否有遗漏的引用内容,比如引用[1]提到结合位图布隆过滤器的优点,这可能是在实际应用中的优化策略,需要在总结部分提到。同时,用户要求生成相关问题,需要根据内容设计相关问题,比如应用场景、实现细节、优缺点比较等。 需要注意回答的结构,先讲位图,再讲布隆过滤器,每个部分包括概念、实现、应用,最后总结对比。确保引用标识正确添加,例如在提到布隆过滤器概念时加上[^4],在缓存穿透时引用[^5]等。</think>### C++位图与布隆过滤器总结 #### 一、位图(Bitmap) **1. 概念与实现** 位图通过每个比特位(bit)表示一个数据的存在性(0/1),适用于海量数据的快速存在性判断[^3]。例如,40亿个整数(约16GB)用位图仅需约500MB内存。 **C++实现示例**: ```cpp class Bitmap { private: std::vector<uint32_t> bits; // 使用uint32_t数组存储位 public: Bitmap(size_t range) : bits((range >> 5) + 1, 0) {} // 计算需要多少32位块 void set(size_t x) { size_t index = x >> 5; // 确定数组下标 size_t offset = x % 32; // 确定比特位偏移 bits[index] |= (1 << offset); } bool test(size_t x) { size_t index = x >> 5; size_t offset = x % 32; return (bits[index] & (1 << offset)) != 0; } }; ``` **2. 应用场景** - **数据去重**:如统计40亿整数中不重复的数字。 - **快速查询**:判断IP是否在黑名单中。 - **排序**:对有限范围的整数进行非比较排序[^2]。 --- #### 二、布隆过滤器(Bloom Filter) **1. 概念与实现** 布隆过滤器通过$k$个哈希函数将元素映射到多个位,若所有对应位为1则“可能存在”,否则“一定不存在”[^4]。牺牲一定准确性换取极高的空间效率。 **C++实现核心逻辑**: ```cpp class BloomFilter { private: Bitmap bitmap; size_t k; // 哈希函数数量 public: BloomFilter(size_t size, size_t k) : bitmap(size), k(k) {} void add(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); bitmap.set(hash % bitmap.size()); } } bool contains(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); if (!bitmap.test(hash % bitmap.size())) return false; } return true; // 可能存在(有一定误判率) } }; ``` **2. 应用场景** - **缓存穿透防护**:拦截不存在的数据请求,保护数据库。 - **分布式系统**:减少节点间冗余数据传输。 - **爬虫URL去重**:避免重复抓取已处理的页面[^3]。 **3. 优缺点** - **优点**:空间效率高,查询时间$O(k)$,适合海量数据[^4]。 - **缺点**:误判率随元素增加而上升,且不支持删除(除非引入计数布隆过滤器)。 --- #### 三、对比与总结 | **特性** | **位图** | **布隆过滤器** | |------------------|------------------------------|------------------------------| | **数据范围** | 适用于整数且范围较小 | 支持任意数据类型 | | **误判率** | 无 | 有(可调整哈希函数数量优化) | | **删除支持** | 直接修改位即可 | 需额外结构(如计数位图) | | **典型场景** | 精确存在性判断 | 允许误判的存在性预筛 | **混合优化方案**:结合位图与布隆过滤器,例如用位图处理高频数据,布隆过滤器处理低频数据,提升整体性能[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值