【数据结构】【项目】BitMap?40亿电话号码如何快速去重?

news2024/9/20 10:46:21

在这里插入图片描述

目录

  • 前言
  • 实现
    • 完整代码
  • 参考资料

前言

40亿电话号码如何快速去重?我们往往会想到bitmap

数据结构中的 Bitmap 是一种位图索引非常高效的数据结构,用于存储处理大规模数据的位信息,其中每个位对应于一个元素,如果位为1,则表示该元素存在于集合中,否则表示不存在。如果要表示一个包含 10 个元素的数据集,可以创建一个包含 10 位的位数组。

在这里插入图片描述

Bitmap 支持插入和查找。插入操作将对应位置的位从 0 设置为 1,将元素添加到数据集中。查找操作通过检查相应位置的位来确定元素是否存在于数据集中。如果位为 1,表示元素存在;如果为 0,表示元素不存在。我们把数字遍历一遍计算放到数组后,就已经是顺序存放的了,遍历取到的就是已经排序后的结果。

Bitmap 非常高效,时间复杂度是O(n)。这是因为位操作本身非常快,并且不受数据集大小的限制。并且Bitmap空间占用非常小,可以大大减小内存消耗。

Bitmap数据结构在搜索引擎、数据库、网络协议等领域都有广泛的应用:

布隆过滤器(Bloom Filter):可以用于快速检查一个元素是否属于一个大型数据集的概率数据结构。

数据库索引:Bitmap 索引可以用于加速数据库查询操作。

网络流量分析:Bitmap 可以用于跟踪网络流量中的 IP 地址、用户 ID 等信息。

此外,Bitmap 适用于离散的、小范围的整数数据。对于连续范围或具有大范围整数的数据集,Bitmap 可能会变得非常大,这种情况下,其他数据结构比如哈希表或树可能更合适。

优点:

运算效率高,不需要进行比较和移位。

空间占用少。

缺点:

所有的数据不能重复。即不可对重复的数据进行排序和查找。

只有当数据比较密集时才有优势。

实现

给定一个bit数组:

    private long[] bits;

当我们需要插入一个数 num,需要计算这个 num在整个bit数组中应该在哪个位置:


    /**
     * 得到long[]的index
     * index表示num中包含多少个64bit可以被整除
     */
    public int getIndex(long num){
        // num/64
        return (int) num / BIT_SIZE;
    }

    /**
     * 得到num在64bit数组上的分布
     * position表示num中整除了64bit后还余下多少bit
     */
    public long getPosition(long num){
        // num%64
        return (long) num % BIT_SIZE;
    }

我们需要知道一个num能映射出多少个bit。

首先long类型相当于64bit,num整除64,计算num中有多少个long,得到 index。而后再num%64,取整除64后的余数,得到position。这两者加起来其实就是num的总bit数,他们共同能够索引到num能映射到的bit位。

我们有插入函数如下:

    /**
     * 添加num
     * 根据num包含多少个long确定在bitmap中的index,根据num÷取余确定在bitmap中除不尽的bit占用
     */
    public void add(long num){
        int index = getIndex(num);
        long position = getPosition(num);
        // 将1左移position位后,position那个位置就是1,
        // 然后数组的index位置做与运算,这样index索引中的position位置就替换成1了
        bits[index] = bits[index] | (1 << position);
    }

只要确定的num的bit占用,我们把数组中对应bit位置置为1即可,这个位置就唯一代表我们插入的num。查询也是同理。

 
    /**
     * 判断指定数字num是否存在
     */
    public boolean contains(int num){
        int index = getIndex(num);
        long position = getPosition(num);
        // 将1左移position后,position那个位置就是1,然后和以前的数据做与运算,判断是否为0即可
        return (bits[index] & 1 << position) != 0;
    }

完整代码

我们添加了测试代码,首先电话号码是11位的数字,但是你会发现对于java来说11位数字太长太大了,没办法直接存。我们把11位的电话号码拆分成前7位和后4位两部分,分成两个bitmap来分别判断,当电话号码的前7位也存在重复,后4位也存在重复,则认为这个电话号码是重复的。

完整代码如下:

BitMap.java

package org.example.bitmap;

import java.util.BitSet;
import java.util.Random;

/**
 * 位图
 */
public class BitMap {

    private long[] bits;

    private int BIT_SIZE =64;

    public BitMap() {
        bits = new long[93750000];
    }

    public BitMap(int n) {
        bits = new long[n];
    }

 
    /**
     * 添加num
     * 根据num包含多少个long确定在bitmap中的index,根据num÷取余确定在bitmap中除不尽的bit占用
     */
    public void add(long num){
        int index = getIndex(num);
        long position = getPosition(num);
        // 将1左移position位后,position那个位置就是1,
        // 然后数组的index位置做与运算,这样index索引中的position位置就替换成1了
        bits[index] = bits[index] | (1 << position);
    }
 
    /**
     * 判断指定数字num是否存在
     */
    public boolean contains(int num){
        int index = getIndex(num);
        long position = getPosition(num);
        // 将1左移position后,position那个位置就是1,然后和以前的数据做与运算,判断是否为0即可
        return (bits[index] & 1 << position) != 0;
    }

    /**
     * 重置num在位图的索引位置
     */
    public void clear(long num){
        int index = getIndex(num);
        long position = getPosition(num);
        // 对1进行左移position个位置,然后取反,最后与byte[index]进行与操作
        bits[index] &= ~(1 << position);
    }

    /**
     * 得到long[]的index
     * index表示num中包含多少个64bit可以被整除
     */
    public int getIndex(long num){
        // num/64
        return (int) num / BIT_SIZE;
    }

    /**
     * 得到num在64bit数组上的分布
     * position表示num中整除了64bit后还余下多少bit
     */
    public long getPosition(long num){
        // num%64
        return (long) num % BIT_SIZE;
    }

    public int cardinality() {
        int sum = 0;
        for (int i = 0; i < bits.length; i++) {
            sum += Long.bitCount(bits[i]);
        }
        return sum;
    }

    public static void main(String[] args) {
        // 假设有40亿手机号
        long numberOfPhoneNumbers = 4_000_000_000L;
        // 存储前7位,最多10,000,000个可能的组合
        BitMap first7DigitsSet = new BitMap(10_000_000);
        // 存储后4位,最多10,000个可能的组合
        BitMap last4DigitsSet = new BitMap(10_000);

        long time = System.currentTimeMillis();
        // 模拟手机号数据,将已存在的手机号设置为true
        for (long i = 0; i < numberOfPhoneNumbers; i++) {
            // 手机号
            String phoneNumber = generateRandomPhoneNumber();

            // 提取手机号的前7位和后4位
            String first7Digits = phoneNumber.substring(0, 7);
            String last4Digits = phoneNumber.substring(7);
            // 将前7位转换为长整数作为位索引
            long first7DigitsIndex = Long.parseLong(first7Digits);
            // 将后4位转换为整数作为位索引
            int last4DigitsIndex = Integer.parseInt(last4Digits);

            // 检查前7位 后4位是否重复
            if (first7DigitsSet.contains((int) first7DigitsIndex) && last4DigitsSet.contains(last4DigitsIndex)) {
                // 打印重复的号码
                StringBuilder sb = new StringBuilder(first7Digits);sb.append(last4Digits);
                System.out.println("重复的号码:" + sb.toString());
            } else {
                // 保存不重复的电话号码
                first7DigitsSet.add((int) first7DigitsIndex);
                last4DigitsSet.add(last4DigitsIndex);
            }
        }
        time = System.currentTimeMillis() - time;

        int cardinality = first7DigitsSet.cardinality() + last4DigitsSet.cardinality();
        System.out.println(cardinality);
        System.out.println(time);
    }

    /**
     * 随机电话号码生成
     */
    private static String generateRandomPhoneNumber() {
        Random random = new Random();

        // 随机生成国家和地区代码(假设国家代码为+1,地区代码为区号)
        String countryCode = "+1";
        // 随机生成三位区号
        String areaCode = String.format("%03d", random.nextInt(1000));
        // 随机生成手机号码三位前缀、四位后缀
        String prefix = String.format("%03d", random.nextInt(1000));
        String suffix = String.format("%04d", random.nextInt(10000));

        // 拼接生成的手机号码
        String phoneNumber = countryCode + areaCode + prefix + suffix;

        return phoneNumber;
    }

}


我们直接for循环把40亿个电话生成放入bitmap中,如果bitmap中已存在就打印,不存在则插入。

运行结果如下:

在这里插入图片描述

40亿太多了没等运行完,执行过程中控制台一直在打印,其实速度非常快

参考资料

https://blog.csdn.net/caox_nazi/article/details/95340537

https://blog.csdn.net/jj89929665/article/details/123539866

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1005906.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何实现不同MongoDB实例间的数据复制?

作为一种Schema Free文档数据库&#xff0c;MongoDB因其灵活的数据模型&#xff0c;支撑业务快速迭代研发&#xff0c;广受开发者欢迎并被广泛使用。在企业使用MongoDB承载应用的过程中&#xff0c;会因为业务上云/跨云/下云/跨机房迁移/跨地域迁移、或数据库版本升级、数据库整…

相机HAL

相机HAL 1、概览实现 HAL2、相机 HAL2.1 AIDL 相机 HAL2.2 相机 HAL3 功能2.3 Camera HAL1 概览 相机 HAL 相机 实现 HAL android12-release 1、概览实现 HAL HAL 位于 相机驱动程序 和 更高级别的 Android 框架 之间&#xff0c;它定义您必须实现的接口&#xff0c;以便应用…

Python解析MDX词典数据并保存到Excel

原始数据和处理结果&#xff1a; https://gitcode.net/as604049322/blog_data/-/tree/master/mdx 下载help.mdx词典后&#xff0c;我们无法直接查看&#xff0c;我们可以使用readmdict库来完成对mdx文件的读取。 安装库&#xff1a; pip install readmdict对于Windows平台还…

Vue3路由

文章目录 Vue3路由1. 载入vue-router 库2. 实例2.1 Vue.js vue-router 实现单页应用2.2 router-link创建链接2.3 router-view显示与url对应组件2.4 <router-link> 相关属性 Vue3路由 1. 载入vue-router 库 Vue.js 路由需要载入vue-router 库 安装直接下载地址&#xf…

Android Aidl跨进程通讯(四)--接口回调,服务端向客户端发送数据

学更好的别人&#xff0c; 做更好的自己。 ——《微卡智享》 本文长度为3325字&#xff0c;预计阅读9分钟 前言 前几篇介绍了AIDL通讯的基础&#xff0c;进阶和异常捕获&#xff0c;本篇就来看看服务端怎么向客户端来实现发送消息。 实现服务端往客户端发送消息&#xff0c;主要…

6.2.3 【MySQL】InnoDB的B+树索引的注意事项

6.2.3.1 根页面万年不动窝 B 树的形成过程是这样的&#xff1a; 每当为某个表创建一个 B 树索引&#xff08;聚簇索引不是人为创建的&#xff0c;默认就有&#xff09;的时候&#xff0c;都会为这个索引创建一个 根节点 页面。最开始表中没有数据的时候&#xff0c;每个 B 树…

S/4 FI之FBL3N/FBL3H/FAGLL03/FAGLL03H的区别

SAP 系统中&#xff0c;为了显示财务凭证行项目&#xff0c;由于不同的时间开发的功能&#xff0c;但实际在使用的过程&#xff0c;到底有些什么样区别&#xff1f; 本文档就是想对这一个问题做一个整体上的说明。 FBL3N&#xff0c;就是传统的行项目报表&#xff0c;在最早的…

刷刷刷——双指针算法

双指针算法 这里的双指针&#xff0c;可能并不是真正意义上的指针&#xff0c;而是模拟指针移动的过程。 常见的有两种&#xff1a; 双指针对撞&#xff1a; 即在顺序结构中&#xff0c;指针从两端向中间移动&#xff0c;然后逐渐逼近 终止条件一般是&#xff1a; left ri…

MATLAB中ischange函数用法

目录 语法 说明 示例 均值的变化 线性区的变化 矩阵数据 ischange函数的功能是查找数据中的突然变化。 语法 TF ischange(A) TF ischange(A,method) TF ischange(___,dim) TF ischange(___,Name,Value) [TF,S1] ischange(___) [TF,S1,S2] ischange(___) 说明 ​…

Python实现机器学习(下)— 数据预处理、模型训练和模型评估

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本门课程将介绍人工智能相关概念&#xff0c;重点讲解机器学习原理机器基本算法&#xff08;监督学习及非监督学习&#xff09;。使用python&#xff0c;结合sklearn、Pycharm进行编程&#xff0c;介绍iris&#xff08;鸢尾…

windows10搭建RocketMq

windows10搭建RocketMq 文章目录 windows10搭建RocketMq1.下载二进制RocketMq2.配置环境变量3.启动4.RocketMq控制台安装 1.下载二进制RocketMq 下载链接 2.配置环境变量 变量名:ROCKETMQ_HOME变量值:MQ解压路径 修改runbroker.cmd和runserver.cmd文件 把%CLASSPATH%用引…

SpringBoot + Prometheus + Grafana 打造可视化监控

SpringBoot Prometheus Grafana 打造可视化监控 文章目录 SpringBoot Prometheus Grafana 打造可视化监控常见的监控组件搭配安装Prometheus安装Grafana搭建SpringBoot项目引入依赖示例:监控SpringBoot内置Tomcat线程池的情况grafana创建监控看板 后台SpringBoot服务添加自…

【深度学习】 Python 和 NumPy 系列教程(十):NumPy详解:2、数组操作(索引和切片、形状操作、转置操作、拼接操作)

目录 一、前言 二、实验环境 三、NumPy 0、多维数组对象&#xff08;ndarray&#xff09; 1. 多维数组的属性 1、创建数组 2、数组操作 1. 索引和切片 a. 索引 b. 切片 2. 形状操作 a. 获取数组形状 b. 改变数组形状 c. 展平数组 3. 转置操作 a. 使用.T属性 b…

Redis模块四:常见的数据类型和使用

目录 Redis 的 5 大基础数据类型 ①字符串类型(String) ②字典类型(Hash) ③列表类型(List) ④集合类型(Set) ⑤有序集合类型(ZSet) Redis 的 5 大基础数据类型 String——字符串类型 Hash——字典类型 List——列表类型 Set——集合类型 ZSet——有序集合类型 …

后发而先至的腾讯混元大模型,到底有哪些技术亮点?

2023年的夏天已经结束了&#xff0c;但是&#xff0c;围绕AIGC大模型的关注热度&#xff0c;却丝毫没有衰退的意思。 在过去的大半年里&#xff0c;我们亲眼见证了大模型浪潮的崛起&#xff0c;甚至可以说是疯狂。截止7月&#xff0c;国内的大模型数量&#xff0c;已经超过130个…

MySQL与ES数据同步之异步调用

文章目录 简述SpringBoot项目引入依赖配置文件项目结构实体类配置类RabbitMQ交换机队列声明&#xff0c;绑定配置类回调接口配置类 Mapper接口UserMapper接口UserEsMapper Controller类Service接口Service实现类监听类/消费者 简述 上一篇是同步调用&#xff0c;我们在中间加上…

【海思SS626 | 开发环境】VMware17安装Ubuntu 18.04.6

目录 一、下载 Ubuntu 18.04.6 LTS二、VMware17创建虚拟机三、安装Ubuntu18.04LTS四、安装其他软件五、总结 一、下载 Ubuntu 18.04.6 LTS 问题&#xff1a;为什么要下载 Ubuntu18.04.6 LTS 而不是使用最新的&#xff0c;或者其他Linux发行版&#xff1f; 答&#xff1a;在ss6…

Python 图形化界面基础篇:使用框架( Frame )组织界面

Python 图形化界面基础篇&#xff1a;使用框架&#xff08; Frame &#xff09;组织界面 引言什么是 Tkinter 框架&#xff08; Frame &#xff09;&#xff1f;步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建框架&#xff08; F…

如何做到安全上网

随着信息化的发展&#xff0c;企业日常办公越来越依赖互联网&#xff0c;而访问互联网过程中&#xff0c;会遇到各种各样不容忽视的风险&#xff0c;例如员工主动故意的数据泄漏&#xff0c;后台应用程序偷偷向外部发信息&#xff0c;木马间谍软件的外联&#xff0c;以及各种挖…

聚观早报 | 荣耀V Purse定档;哪吒S迎来最新OTA升级

【聚观365】9月13日消息 荣耀V Purse定档 哪吒S迎来最新OTA升级 宝马将向其英国工厂投资7.5亿美元 英伟达称霸AI芯片领域致初创公司融资难 甲骨文第一财季收入约125亿美元增长9% 荣耀V Purse定档 不久前&#xff0c;荣耀官方推出了全新的荣耀Magic V2内折叠屏旗舰&#x…