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

2023-09-13 09:58:06

在这里插入图片描述

前言

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位的电话号码拆分成前后两部分,分成两个bitmap来分别判断,当电话号码的两部分存在重复则认为这个电话号码是重复的。

完整代码如下:

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;
        // 存储前3位,最多100个可能的组合
        BitMap firstDigitsSet = new BitMap(100);
        // 存储后8位,最多 100_000_000 个可能的组合
        BitMap lastDigitsSet = new BitMap(100_000_000);

        long time = System.currentTimeMillis();

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

            // 提取手机号的前3位和后8位
            String firstDigits = phoneNumber.substring(0, 3);
            String lastDigits = phoneNumber.substring(3);
            int firstDigitsIndex = Integer.parseInt(firstDigits);
            int lastDigitsIndex = Integer.parseInt(lastDigits);

            // 检查前2位是否重复
            if (firstDigitsSet.contains(firstDigitsIndex)) {
                // 检查后9位是否重复
                if (lastDigitsSet.contains(lastDigitsIndex)) {
                    // 打印重复的号码
                    StringBuilder sb = new StringBuilder(firstDigits);sb.append(lastDigits);
                    System.out.println("重复的号码:" + sb.toString());
                } else {
                    // 不重复则存储号码后9位
                    lastDigitsSet.add(lastDigitsIndex);
                }

            } else {
                firstDigitsSet.add(firstDigitsIndex);
            }

        }

        time = System.currentTimeMillis() - time;
        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

更多推荐

MyBatis 分页插件 PageHelper

文章目录前言PageHelper应用实现原理剖析应用场景分析前言分页插件PageHelper是我们使用Mybatis到的比较多的插件应用,适用于任何复杂的单表、多表分页查询操作。本文介绍PageHelper的使用及原理。PageHelper应用添加依赖<dependency><groupId>com.github.pa

VMware Workstation Pro各版本下载安装教程

VMwareWorkstationPro下载打开浏览器,输入VMwareWorkstationPro找到VMwareWorkstationPro官网并点击进入,官网地址:https://www.vmware.com/cn/products/workstation-pro.html进入官网首页后可以下载最新版本的VMwa

DBAPI安装教程

安装教程请先下载安装包。默认账户admin/admin。为了便于您理解安装的时候需要配置的参数,请您先学习日志监控相关的功能设计本地部署单机版依赖java环境,先自行在服务器安装jdk8+,并配置环境变量下载安装包解压到需要安装的目录修改conf/application.properties文件中的以下配置#api访问

谷粒商城----rabbitmq

一、为什么要用MQ?三大好处,削峰,解耦,异步。削峰比如秒杀,或者高铁抢票,请求在某些时间点实在是太多了,服务器处理不过来,可以把请求放到MQ里面缓冲一下,把一秒内收到的1万个请求放到队列里面,花10分钟去消费队列里的请求。解耦比如有一个服务A每天都采集数据并计算各种数据,服务B需要调用服务A的接口获取数据,就在A开一

使用 PyTorch 的计算机视觉简介 (2/6)

一、说明在本单元中,我们从最简单的图像分类方法开始——一个全连接的神经网络,也称为感知器。我们将回顾一下PyTorch中定义神经网络的方式,以及训练算法的工作原理。二、数据加载的实践首先,我们使用pytorchcv助手来加载所有数据。!wgethttps://raw.githubusercontent.com/Micr

Buuctf web [SUCTF 2019]EasySQL

又是一道考察sql注入的题1、起手试探(主要看看输入什么内容有正确的回显)101'1'#发现只有在输入1的情况下有正常的回显,输入0或其他字符都没有回显,所以这题就要尝试堆叠注入了。ps:(如果想尝试其他注入方法,输入以下内容需要有回显1'报错1'#正确)2、爆库1;showdatabases;3、报表1;showta

dart 学习 之 字符串插值,空变量 null,避空运算符,条件属性访问,集合字面量,箭头语法

文章目录字符串插值(Stringinterpolation)空变量null避空运算符条件属性访问集合字面量箭头语法字符串插值(Stringinterpolation)下面是一些使用字符串插值的例子:Herearesomeexamplesofusingstringinterpolation:Stringresult字符串

【实战详解】如何快速搭建接口自动化测试框架?Python + Requests

摘要:本文主要介绍如何使用Python语言和Requests库进行接口自动化测试,并提供详细的代码示例和操作步骤。希望能对读者有所启发和帮助。前言随着移动互联网的快速发展,越来越多的应用程序采用WebAPI(也称为RESTfulAPI)作为数据交换的主要方式。针对API进行自动化测试已经变得非常重要,它可以让我们快速地

Vue Hooks 让Vue开发更简单与高效

VueHooks让Vue开发更简单与高效介绍VueHooks是一个基于Vue.js的插件,它提供了一种新的方式来编写Vue组件,使得开发更加简单和高效。它借鉴了ReactHooks的概念,通过使用Hooks,我们可以在不编写类组件的情况下,实现状态管理和生命周期处理。为什么使用VueHooks在传统的Vue开发中,我们

【c语言】详解结构体

目录什么是结构体?结构体的声明结构体变量的创建和初始化匿名结构体类型结构体的自引用结构体的初始化普通初始化指定初始化结构体内存对齐对齐规则默认对齐数的修改结构体传参什么是结构体?在学习每个类型之前我们需要了解其存在的意义,即什么是结构体?为什么要引入结构体这个类型呢?我们可以想象现实中我们是如何处理一个人信息的?假设现

Win10 家庭版 - 解决应用程序无法启动,因为应用程序的并行配置不正确的问题(System Default Context”的激活上下文生成失败)

Win10家庭版-解决应用程序无法启动,因为应用程序的并行配置不正确的问题(SystemDefaultContext”的激活上下文生成失败)系统环境遇到问题试过过程解决办法前天的时候,女盆友公司电脑遇到个问题:几乎所有的exe程序和软件都不能启动或者运行。我的第一个解决办法:重装即可。结果人家嫌挨个重装太麻烦。于是乎,

热文推荐