0基础了解 java 位图 过滤器 海量数据处理

目录

一.位图

1.引入

2.位图

3.图片演示

4.代码演示

5.自己实现

检验>:

二.布隆过滤器

1.引入

2.布隆过滤器

3.为什么可能会存在?

4.代码演示

5.自己实现

检验>:

6.过滤器的删除

7.优点

8.缺点

9.使用场景

三.海量数据查找

1.引入

2.hash切割

(1).问题:

(2).思路

(3).问题变式

3.位图应用

(1).问题一

(2).问题二

(3).问题三

4.布隆过滤器

(1).问题一:

(2).问题二


一.位图

1.引入

假设现在有40亿个不重复数据,需要快速确定某一个数是否在里面,怎么解决?

通常遍历数组挨个找,而40亿整形数据 = 14.9016G,对时间与空间浪费太多了

正确处理为采用位图,消耗的内存为 1.862G,更加方便查找

2.位图

又称bitMap,是hash的应用之一,通过某种映射关系将 数据的映射在bit位 上,并用数字代表存在状态, 1代表存在,0代表不存在,通常解决某个数据是否在该不重复的数据集合中

通常解决某个数据是否在该数据集合中

3.图片演示

4.代码演示

java 复制代码
public class test {
    public static void main(String[] args) {
        // 这里参数可以给 位数
        // 也可以不给,不够会自动扩容
        BitSet bitSet = new BitSet();
        int[] nums = {1,21,25,36,23,58};
        for(int x : nums)bitSet.set(x);
        System.out.println(bitSet.equals(58));
    }
}
//  结果: true

这里面有非常多的方法,可以去看

5.自己实现

java 复制代码
public class MyBitSet {
    public byte[] element;
    public int size;
    public MyBitSet(){
        element = new byte[1];
    }


    /**
     *
     * @param n  表示位数
     */
    public MyBitSet(int n){
        // 当 n = 13位时, 需要 2 个byte元素
        // n / 8 只有一个元素
        // n / 8 + 1是两个
        element = new byte[n / 8 + 1];
    }
    private void capcaticy(int x){
        if(size > element.length * 8 ){
            element = Arrays.copyOf(element,element.length * 2);
        }
        if(x / 8 + 1 > element.length)
            element = Arrays.copyOf(element,x / 8 + 1);
    }

    /**
     * 将 x 对应的下标改为 1
     * @param x
     */
    public void set(int x){
        // 检查是否为负数
        if(x < 0)throw new IndexOutOfBoundsException("元素为负数.映射下标出错");
        // 检查容量
        capcaticy(x);
        int index = x / 8;
        int eleStress = x % 8;
        element[index] |= (1 << eleStress);
        size++;
    }

    /**
     * 将 x 对应的下标改为 0
     * @param x
     */
    public void reSet(int x){
        // 检查是否为负数
        if(x < 0)throw new IndexOutOfBoundsException("元素为负数.映射下标出错");
        // 检查容量
        capcaticy(x);
        int index = x / 8;
        int eleStress = x % 8;
        element[index] &= ~(1 << eleStress);
        size--;
    }

    /**
     * 判断当前 x 的位置 是否为 1
     */
    public boolean get(int x){
        // 检查是否为负数
        if(x < 0)throw new IndexOutOfBoundsException("元素为负数.映射下标出错");
        // 检查容量
        capcaticy(x);
        int index = x / 8;
        int eleStress = x % 8;
        if((element[index] & (1 << eleStress)) == 0)return false;
        return true;
    }
    

    /**
     * 获得元素个数
     */
    public int getSize(){return size;}
}

检验>:

二.布隆过滤器

1.引入

在日常的生产环境中,总要判断某个元素是否 在集合中,若是整数,可以用位图解决;若是字符串其他类型,位图就不行了;若用hash解决,虽然hash的插入查找都是O(1),但插入大量的数据也会造成内存消耗大,而hash+ 位图可以很好解决

2.布隆过滤器

采用hash+位图 的方式,可以快速查找某个元素不存在可能存在 ,称为概率型结构

特点:提高查找效率,节省内存空间

注意:布隆过滤器 并没有存入元素,只是以映射的关系存储,这意味着它很安全

3.为什么可能会存在?

先了解原理,给定一个字符串str = "abc",通过多个hash函数求出 str 的地址,分别在对应位图标记

到时候判断是否在该集合,可以分别对这些位图判断对应下标是否为1,若都为1,则存在;若有一个为0,则不存在

但是万一出现一个字符串 str2 = "bit",每一个hash地址在位图中的位置与 str 一样,但还未存储

此时结合上述的判断方法就会显示 str2 是存在的,这就出现问题了,所以是可能存在

4.代码演示

在java中没有提供对应的类,可以到工具类guava找,具体看这个博客

布隆过滤器:https://www.cnblogs.com/zc110/articles/13380446.html

5.自己实现

java 复制代码
class SimpleHash{
    // 容量
    public int cap;
    // 种子
    public int seed;
    public SimpleHash(int cap,int seed){
        this.cap = cap;
        this.seed = seed;
    }
    /**
     *  根据种子表示hash函数
     */
    public int hash(String str){
        int h;
        // 求地址 (n - 1) & hash
        return str == null ? 0 : seed * (cap - 1) & ((h = str.hashCode()) ^ (h >>> 16));
    }
}
public class MyBloomFiter {
    // 位图
    public BitSet bitSet;
    // 记录存储杜少元素
    public int sizeElement;
    // 种子,用于确定不同的 hash 函数
    public int[] seeds = new int[]{2,3,4,5,6,7};
    // 定义 hash函数 数组
    public SimpleHash[] simpleHashes ;
    // 定义位数
    // hash函数容量为 2 ^ n,方便计算
    public static final int DEFFAULT_CAPCATICY = 1 << 20;
    public MyBloomFiter(){
        bitSet = new BitSet();
        simpleHashes = new SimpleHash[seeds.length];
        for (int i = 0;i < simpleHashes.length;i++)
            simpleHashes[i] = new SimpleHash(DEFFAULT_CAPCATICY,seeds[i]);
    }
    /**
     *  添加元素
     */
    public void add(String str){
        if(str == null)return;
        for(int i = 0; i < simpleHashes.length;i++){
            int index = simpleHashes[i].hash(str);
            bitSet.set(index);
        }
    }
    /**
     *  判断字符串不存在或可能存在
     */
    public boolean contains(String str){
        if(str == null)return false;
        for(int i = 0; i < simpleHashes.length;i++){
            int index = simpleHashes[i].hash(str);
            if(!bitSet.get(index))return false;
        }
        return true;
    }

}

检验>:

java 复制代码
    // bloomFiter 演示
    public static void main(String[] args) {
        MyBloomFiter myBloomFiter = new MyBloomFiter();
        String[] str = new String[]{"张三","李四","王五","赵六","田七"};
        for (String s : str) {
            myBloomFiter.add(s);
        }
        System.out.println(myBloomFiter.contains("田七"));//true
        System.out.println(myBloomFiter.contains("赵钱"));//false
    }

6.过滤器的删除

假设现在有 str1 = "abc" str2 = "bit",在位图有以下关系

当把 str1 的映射关系置为 0,那么同时也删除了 str2 的关系,后续的查找就会出现问题

所以使用它不用考虑删除,即过滤器不支持删除

7.优点

(1).查找的时间复杂度为O(k)(k为过滤器的hash函数数量)

(2).hash函数之间没有任何关系,方便运行

(3).不能存储元素本身,存储的只是映射关系,在保密性强的工作会有优势
(4). 在能够承受一定的误判 时,布隆过滤器比其他数据结构有这很大的空间优势(建议看4的博客)
(5). 数据量很大时,布隆过滤器可以表示全部数据减少空间消耗,其他数据结构不能
(6). 使用同一组散列函数 的布隆过滤器可以进行交、并、差运算(下面有讲到)

8.缺点

(1).有误判率,不能准确判断是否在集合中
(2).能不能获取元素本身
(3).不能删除元素
(4). 如果采用计数方式 删除,可能会存在计数回绕问题(详细看三标题布隆过滤器)

9.使用场景

(1). 一般用过 google guava 包实现 Bloom Filter
(2). 网页爬虫对 URL 的去重,避免爬去相同的 URL 地址。
(3) 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否是垃圾邮箱。
(4). 解决数据库缓存击穿,黑客攻击服务器时,会构建大量不存在于缓存中的 key 向服务器发起请求,在数据量足够大的时候,频繁的数据库查询会导致挂机。
(5). 秒杀系统,查看用户是否重复购买。

三.海量数据查找

1.引入

面试中最常被问到 有100亿个数据,某个元素 x 在不在里面?此处就可以解决这类问题

2.hash切割

(1).问题:

给一个超过 100G 大小的 log file, log 中存着 IP 地址 , 设计算法找到出现次数最多的 IP地址?

(2).思路

思路一 :遍历数组,放入hash中,在把次数最多的IP打印 ---直接pass,存储查找的时间复杂度太大了

思路二 :对于大数据查找,首先也是最重要 的:如何把大数据分为若干个小数据 ,还不影响查找呢?

可以将100g文件分为n个(这里假设n = 200),接着统计每个文件IP个数,然后比较选最多的

但面临另一个问题:我们按照数量分为200个,每个文件的数目最多的IP如何确保是整个数目最多的呢?? --- 直接pass

思路三:仿照思路二按照元素分开,每个文件存储一种数据,接着遍历文件Map统计一下就可以了

(3).问题变式

与上题条件相同, 如何找到top K的IP?

与上面的一样,知道元素与出现次数,仿照优先级队列求前top k大的数 ,创建只放k个元素小根堆,每次堆顶元素与后续元素比大小,若是小于后续元素,则交换位置,到最后输出即可(需要重写比较方法)

3.位图应用

(1).问题一

给定100亿个整数,设计算法找到只出现一次的整数?

思路一:hash切割,与上面思路的一样,都是分割n个小文件,把每个数hash到对应文件,最后遍历文件Map统计一下即可找出

思路二:

首先整形约有42.95亿个,题中一定存在重复数;用位图表示42.95亿整形,512MB就全部存储了,所以用两个位图解决

思路三:

采用一个位图表示,用两个位表示出现次数

(2).问题二

给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

思路一 :2个hash分割,分别把100亿整形分为200个文件,通过同一个hash函数求得下标,数据一样hash地址一样,所以接着遍历文件,判断i 下标的文件对应的值是否一样,若一样,则保存.但是 内存大小为200亿整形大小,pass

思路二:两个512MB位图+循环

分别遍历文件,通过一样hash函数放入位图中,接着遍历位图,判断对应为是否都为 1,若存在,则保存起来

思路三:两个512MB位图+位操作

问题变式:

其他条件不变,求并集 差集?

求并集,bitSet1 | bitSet2 即可

求差集, bitSet1 - bitSet2 = (bitSet1 ^ bitSet2) & bitSet1

(3).问题三

位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数

思路一:hash切割,分为200个文件,对数据求hash地址放入对应的文件中,遍历文件,依次Map查找出现次数两次的即可 --- 消耗内存不满足题意,pas

思路二 :两个位图,与问题一的思路二一样 ,不过最后循环判断为是否对应位为 1 1 或 是否为 0 0,若不是,则保存

思路三 :一个位图,与问题一的思路三一样 ,不过最后循环判断为是否对应位为 1 1 或 是否为 0 0,若不是,则保存

4.布隆过滤器

(1).问题一:

给两个文件,分别有100亿个query,如何找到两个文件交集?分别给出精确算法和近似算法

精确算法:hash切割,与位图应用问题而思路一一样

近似算法 :借助bloomFilter

1>.把第一个文件的query翻入过滤器中

2> .遍历第二个文件,查看每一个query是否存在(会存在误判)

**3>.**若存在,则储存起来

(2).问题二

如何扩展BloomFilter使得它支持删除元素的操作

一般采用计数删除

假设布隆过滤器大小为4,即位图大小为4, 能存放32个整形,现有两个hash函数 h1 h2 每次插入元素

假设现在插入两个元素 "abc" "123"

1>.插入"abc",计算hash地址:h1("abc") = 2,h2("abc") = 3

2>:把对应地址放进位图,set(2),set(3)

3> :创建 int[] nums = new int[4] 的整形数组,把hash地址 当成元素,值表示出现次数

此时为:nums: 0 1 1 0

注意: 有个误区,元素在位图中0 下标的 2号 3号位放入,为什么不在nums的0下标++呢???

因为这是同一个元素的不同表示 ,hash值2代表"abc"

位图中,set(2)set(3)相当于把abc在0下标表示

在数组中,nums[2] nums[3]的值++相当于记录一下"abc"出现次数

4>:接着插入"123",h1("123") = 2,h2("123") = 3,重复上述操作,此时数组nums: 0 2 2 0

5>:开始删除 "abc"

第一步: 先求h1 h2

第二步:接着判断元素是否存在位图中,可以在位图中用contains 判断,也可以看对应下标的元素是否都>0 ,若不存在有一个值为0,则表示元素不存在,直接返回;

第三步:对应的下标减减

第四步:再次查看对应下标元素是否都大于0,若有等于0,则表示元素删除成功,位图对应下标置为0

影响:

(1).依然不可避免可能存在

删除 "abc"后,nums: 0 1 1 0,在不改变hash函数的情况下,contains("123")依然会为true

(2).计数循环

一直没有删除 ,疯狂添加元素 ,nums对应下标的值一直加加,某一次到上限后,会变为该类型最小值

相关推荐
MSTcheng.3 小时前
【C++】C++入门—(中)
开发语言·c++
期待のcode3 小时前
SpringMVC的请求接收与结果响应
java·后端·spring·mvc
行走的码农霖悦4 小时前
PHP如何解决使用国密SM4解密Base64数据错误问题?(基于lpilp/guomi)
开发语言·php
CHEN5_026 小时前
【CouponHub开发记录】SpringAop和分布式锁进行自定义注解实现防止重复提交
java·分布式·spring·项目
编啊编程啊程6 小时前
Netty从0到1系列之Selector
java·spring boot·spring cloud·java-ee·kafka·maven·nio
wheeldown6 小时前
【Linux】为什么死循环卡不死 Linux?3 个核心逻辑看懂进程优先级与 CPU 调度密码
linux·运维·服务器·开发语言·c++·unix·进程
xxy.c7 小时前
嵌入式解谜日志-网络编程(udp,tcp,(while循环原理))
linux·运维·c语言·开发语言·数据结构
Want5957 小时前
C/C++哆啦A梦
c语言·开发语言·c++
angushine7 小时前
Spring Boot 工程启动时自动执行任务方法
java·spring boot·后端