23. 数据结构之位图

前言

之前在讲散列表的时候,提到过位图的概念。位图(Bitmap)作为一种特殊的数据结构,它使用一系列位来表示数据,每个位只有两个状态(0或1)。由于它的高效性和节省空间的特性,位图在很多场景中都有广泛的应用。本节,我们就位图展开详细介绍。

1. 简介

位图使用位来表示数据,这使得它在存储和处理大量数据时具有高效性和节省空间的优点。例如,如果我们需要存储一亿个整数,使用普通的数组需要消耗大约381MB的内存(假设一个整数占用4字节,100000000*4/1024/1024=381MB),而使用位图只需要消耗大约11.92MB(100000000/8/1024/1024)的内存。

如下图所示,可以看到一个整型占用四个字节,一个字节八位,那么一个整型可以表示32个数字。那么理论上来说,如果用位图的话,我们的存储空间,相对于直接整型存储,可以缩小32倍,也就是上面的 381MB / 32 约等于 11.92 MB。
在这里插入图片描述

2. 代码实现

2.1 java工具包自带

import java.util.BitSet;
public class Client {
	@Test
	public void testBitMap(){
	    BitSet bitSet=new BitSet(100000000);
	    for (int i = 0; i < 1000000; i++) {
	        bitSet.set(i);
	    }
	    System.out.println(bitSet.get(200));
	    System.out.println(bitSet.get(343535353));
	}
}

代码运行结果:

true
false

2.2 手写实现位图

2.2.1 基础知识

  1. 移位
    移位运算是指将一个数字转换为2进制后向前向后进位的运算,具体计算举例如下:
    1<<2 : 00000001 转换后为 00000100
    8>>3 : 00001000 转换后为 00000001

  2. 与运算规则如下:1 & 0= 0 ,1&1=1 ,0&0=0
    所以可得出结论:通过和1 按位与运算,可以得出对应的比特位是0还是1

  3. 或运算规则如下:0|1=1,1|1=1, 0|0=0
    所以可得出结论:通过与1 按位或运算,最后得到的结果对应比特位一定是1

2.2.2 代码实现

package org.wanlong.hash;

import java.util.Arrays;

/**
 * @author wanlong
 * @version 1.0
 * @description:
 * @date 2023/6/25 14:37
 */
public class MyBitMap {

    //元素存储
    private byte[] elem;
    //已有元素个数
    private int usedSize;

    public MyBitMap() {
        this.elem = new byte[1];
    }

    /**
     * @param n:
     * @return
     * @Description: 初始化 n代表要使用多少位,也即存储的最大范围值
     * @Author: wanlong
     * @Date: 2023/6/25 15:07
     **/
    public MyBitMap(int n) {
        this.elem = new byte[n / 8 + 1];
    }

    /**
     * @param val:
     * @return void
     * @Description: 设置值
     * @Author: wanlong
     * @Date: 2023/6/25 15:05
     **/
    public void set(int val) {
        if (val < 0) {
            throw new IndexOutOfBoundsException();
        }
        //整数除8 得到元素应该放到哪个下标
        int arrayIndex = val / 8;
        //整数除8求余,得到元素在这个下标的整型的哪个比特位
        int bitIndex = val % 8;

        //扩容
        if (arrayIndex > elem.length - 1) {
            elem = Arrays.copyOf(elem, arrayIndex + 1);
        }
        // 或运算 可以保证如果之前插入过这个值,不会影响这个值还是1,也不会更改别的值
        // 1 向左移位 bitindex 后,则对应的bitindex为1 
        // 如 00000001 向左移动 3 则 为 00001000
        //或运算 0|1=1 ,1|1=1 ,则,按位或运算后,固定的位置bitIndex一定是1 ,其他位置不变
        elem[arrayIndex] |= (1 << bitIndex);
        //元素个数加一
        usedSize++;
    }

    /**
     * @param val: 待查找值
     * @return boolean
     * @Description 判断值是否存在
     * @Author: wanlong
     * @Date: 2023/6/25 15:04
     **/
    public boolean get(int val) {
        if (val < 0) {
            throw new IndexOutOfBoundsException();
        }
        int arrayIndex = val / 8;
        int bitIndex = val % 8;

        //如果算出来查找元素下标大于数组长度,一定不在数组中,返回false
        if (arrayIndex >= elem.length) {
            return false;
        }
        // 1 向左移位 bitindex 后,则对应的bitindex为1 
        // 如 00000001 向左移动 3 则 为 00001000
        // 与运算  0&1 =0,1&1=1  则,如果与的结果不为0,一定是对应的bitIndex=1 ,即,元素存在
        if ((elem[arrayIndex] & (1 << bitIndex)) != 0) {
            return true;
        }
        return false;
    }

    /**
     * @param val: 待重置值
     * @return void
     * @Description: 将val的对应位置置为0
     * @Author: wanlong
     * @Date: 2023/6/25 15:06
     **/
    public void delete(int val) {
        if (val < 0) {
            throw new IndexOutOfBoundsException();
        }
        int arrayIndex = val / 8;
        int bitIndex = val % 8;
        //对应下标置为0
        // 1 向左移位 bitindex 后,则对应的bitindex为1 
        // 如 1<<3 , 00000001 向左移动 3 则 为 00001000
        // ~(1<<3), 00001000 , 11110111  对应的bitindex设为0
        // 1&0=0, 0&0=0 
        // 所以,最终对应的下标bitIndex一定是0 
        elem[arrayIndex] &= ~(1 << bitIndex);
        //元素个数减一
        usedSize--;
    }


    /**
     * @return int
     * @Description: 当前位图记录的元素个数
     * @Author: wanlong
     * @Date: 2023/6/25 15:06
     **/
    public int getUsedSize() {
        return usedSize;
    }
}

2.2.3 测试验证

@Test
public void testMyBitMap() {
    MyBitMap myBitMap = new MyBitMap(1000000);
    for (int i = 0; i < 1000000; i++) {
        myBitMap.set(i);
    }
    System.out.println(myBitMap.get(200));
    System.out.println(myBitMap.get(343535353));
}

运行结果:

true
false

3. 优缺点

3.1 优点

  1. 可存储的数据变多了
  2. 占用空间小,索引速度快

3.2 缺点

  1. 可读性差
  2. 位图存储的元素个数虽然比一般做法多,但是存储的元素大小受限于存储空间的大小。位图存储性质:存储的元素个数等于元素的最大值。比如, 1K 字节内存,能存储 8K 个值大小上限为 8K 的元素。(元素值上限为 8K ,这个局限性很大!)比如,要存储值为 65535 的数,就必须要 65535/8=8K 字节的内存。要就导致了位图法根本不适合存 unsigned int 类型的数(大约需要 2^32/8=5 亿字节的内存)。
  3. 位图对有符号类型数据的存储,需要 2 位来表示一个有符号元素。这会让位图能存储的元素个数,元素值大小上限减半。 比如 8K 字节内存空间存储 short 类型数据只能存 8K*4=32K 个,元素值大小范围为 -32K~32K

4. 应用

4.1 大数据去重

当我们需要处理大量的数据,并且需要去除重复的数据时,可以使用位图。例如,我们可以使用位图来记录用户的访问记录,以去除重复的访问。

4.2 布隆过滤器

布隆过滤器是一种使用位图实现的概率型数据结构,它可以用于检测一个元素是否在一个集合中。由于布隆过滤器可能会有误判,所以它通常用于需要快速检查但可以接受一定误判率的场景,例如网页爬虫、垃圾邮件过滤等。

4.3 位图索引

在数据库中,位图索引是一种使用位图来加快数据检索速度的技术。它特别适用于处理低基数数据(即数据的唯一值数量相对较少)。

5. 经典案例

  1. 40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中
    首先,将这40亿个数字存储到bitmap中,然后对于给出的数,判断是否在bitmap中即可。
  2. 使用位图法判断整形数组是否存在重复
    遍历数组,一个一个放入bitmap,并且检查其是否在bitmap中出现过,如果没出现,放入,否则即为重复的元素。
  3. 使用位图法进行整形数组排序
    首先遍历数组,得到数组的最大最小值,然后根据这个最大最小值来缩小bitmap的范围。(这一步很关键,通过确定最大最小值,可以将位图的大小缩小,比如数字范围是3000~~10000时,可以先将数据减去3000,再放入bitmap中,次数bitmap大小会变小),这里需要注意对于int的负数,都要转化为unsigned int来处理,而且取位的时候,数字要减去最小值。
  4. 在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数
    采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)。其实,这里可以使用两个普通的Bitmap,即第一个Bitmap存储的是整数是否出现,如果再次出现,则在第二个Bitmap中设置即可。这样的话,就可以使用简单的1- Bitmap了。

以上,本人菜鸟一枚,如有错误,请不吝指正。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL事务相关笔记

杂项 InnoDB最大特点&#xff1a;支持事务和行锁&#xff1b; MyISAM不支持事务 介绍 一个事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元&#xff0c;只有当事务中的所有操作都正常执行完了&#xff0c;整个事务才会被提交给数据库。事务有如下特性…

使用传统图像处理算法+机器学习进行shadow detection

前言 阴影是图像中常见的现象&#xff0c;它们对于场景理解和分析非常重要。由于阴影区域通常比较暗淡&#xff0c;而且与周围物体区别较大&#xff0c;因此在图像处理和计算机视觉领域中&#xff0c;阴影检测是一个重要的研究方向。传统的阴影检测算法通常基于阈值或边缘检测…

SVM算法的介绍

一、SVM算法的介绍 1.什么是SVM算法&#xff1f; SVM&#xff08;Support Vector Machine&#xff09;是一种常见的监督学习算法&#xff0c;用于进行二分类或多分类任务。它的主要思想是找到一个最优的超平面&#xff0c;将不同类别的样本分隔开。 超平面最大间隔介绍&#…

人体姿态估计技术的理解(Human Pose Estimination)

本人毕设题目是人体姿态估计技术的相关课题&#xff0c;本人按照自己对人体姿态估计技术的学习和理解进行论述&#xff0c;如有不足&#xff0c;请大家指正&#xff01;&#xff01;&#xff01; 首先讨论一个问题&#xff1a;什么是姿态估计? “姿势估计?……姿势这个词对…

opencv如何使用GPU的三种方法

我在工作实验涉及到图像和视频处理时&#xff0c;通常使用opencv提供的库来做处理&#xff0c;虽然OpenCV是一个广泛使用的库&#xff0c;它提供了丰富的功能和工具。然而&#xff0c;有时候在处理大量图片或视频时&#xff0c;我们可能会面临速度受限的问题。 opencv执行图像…

【C/C++】之内存管理(超详细练气篇)

个人主页&#xff1a;平行线也会相交&#x1f4aa; 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】&#x1f48c; 本专栏旨在记录C的学习路线&#xff0c;望对大家有所帮助&#x1f647;‍ 希望我们一起努力、成长&…

基本 SQL 命令 、重要的 SQL命令、SQL 约束 及 SQL语句 的 执行顺序

学习目标&#xff1a; 学习目标如下&#xff1a; SQL语句执行顺序 学习内容&#xff1a; 基本 SQL 命令&#xff1a; FROMONJOINWHEREGROUP BYAGG_FUNCWITHHAVINGSELECT 从数据库中提取数据UNIONDISTINCTORDER BY 排序LIMIT 重要的sql命令&#xff1a; 1、SELECT - 从数据…

Finalshell安全吗?Xshell怎么样?

文章目录 一、我的常用ssh连接工具二、Xshell2.1 下载&#xff1a;认准官网2.2 Xshell 配置2.3 Xftp和WinSCP 一、我的常用ssh连接工具 之前讲过&#xff1a; 【服务器】远程连接选SSH&#xff08;PUTTY、Finalshell、WinSCP&#xff09; 还是 远程桌面&#xff08;RDP、VNC、…

解决 CentOS/Alma 安装 libpcap-devel 报错:No match for argument: libpcap-devel

环境&#xff1a;Alma 8.5、Centos 7.x 解决方案 Linux 安装软件的时候&#xff0c;需要 libpcap-devel 这个组件&#xff0c;执行命令&#xff1a;yum install libpcap-devel &#xff0c;然后报错如下&#xff1a; Last metadata expiration check: 0:05:24 ago on Mon 12…

【算法】数学相关知识总结

文章目录 gcd 和 lcm取模运算 %求一个点和一片矩形区域之间的最短距离 本文用于记录一些关于算法题中偶尔被使用到的数学相关知识。 gcd 和 lcm gcd 和 lcm 分别是 最大公约数&#xff08;Greatest common divisor&#xff09; 和 最小公因数&#xff08;Least Common Multip…

机器学习——决策树算法

一、实验目的 掌握如何实现决策树算法&#xff0c;用并决策树算法完成预测。 二、实验内容 本次实验任务我们使用贷款申请样本数据表&#xff0c;该数据表中每列数据分别代表ID、年龄、高薪、有房、信贷情况、类别&#xff0c;我们根据如下数据生成决策树&#xff0c;使用代…

二值化的mask生成yolov5-7.0的实例分割训练标签

背景&#xff1a;要用yolov5-7.0训练分割&#xff0c;这里使用自己的数据&#xff0c;mask是二值化的数据&#xff0c;要先转换成COCO格式&#xff0c;这里用imantics实现。 详见&#xff1a;https://zhuanlan.zhihu.com/p/427096258 截取部分代码如下图&#xff0c;读取image图…

ninja的简单使用

文章目录 Ninja安装windows环境Linux环境 入门使用与CMake一起使用 Ninja安装 windows环境 问题的解决通常有多种方法。按照结果的好坏程度&#xff0c;可以将解决方法简单的划分为&#xff0c;上中下三个层次&#xff0c;见:为什么谋士总喜欢提上中下三策&#xff1f; 在w…

C++静态和动态链接库导出和使用

1、简介 代码开发过程中会遇到很多已有的函数库&#xff0c;这些函数库是现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&#xff0c;因此库的存在意义非同寻常。 本质上来说库是一种…

在 K8S 中部署一个应用 上

本身在 K8S 中部署一个应用是需要写 yaml 文件的&#xff0c;我们这次简单部署&#xff0c;通过拉取网络上的镜像来部署应用&#xff0c;会用图解的方式来分享一下&#xff0c;过程中都发生了什么 简单部署一个程序 我们可以通过 kubectl run 的方式来简单部署一个应用&#…

测试技术体系

目录&#xff1a; 软件测试分类分层测试体系 1.软件测试分类 软件测试的分类_安全性测试属于功能测试吗_阿瞒有我良计15的博客-CSDN博客 1.单元测试&#xff08;Unit Testing&#xff09;&#xff1a;单元测试是指对软件的最小可测试单元进行测试&#xff0c;例如一个函数、一…

ES+Redis+MySQL,这个高可用架构设计

一、背景 会员系统是一种基础系统&#xff0c;跟公司所有业务线的下单主流程密切相关。如果会员系统出故障&#xff0c;会导致用户无法下单&#xff0c;影响范围是全公司所有业务线。所以&#xff0c;会员系统必须保证高性能、高可用&#xff0c;提供稳定、高效的基础服务。 …

macOS Ventura 13.4.1 (22F82) Boot ISO 原版可引导镜像下载

macOS Ventura 13.4.1 (22F82|22F2083) Boot ISO 原版可引导镜像下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Wind…

TSception:从EEG中捕获时间动态和空间不对称性用于情绪识别

TSception&#xff1a;从EEG中捕获时间动态和空间不对称性用于情绪识别&#xff08;论文复现&#xff09; 摘要模型结构代码实现写在最后 **这是一篇代码复现&#xff0c;原文通过Pytorch实现&#xff0c;本文中使用Keras对该结构进行复现。**该论文发表在IEEE Transactions on…

Spark10-11

10. 广播变量 10.1 广播变量的使用场景 在很多计算场景&#xff0c;经常会遇到两个RDD进行JOIN&#xff0c;如果一个RDD对应的数据比较大&#xff0c;一个RDD对应的数据比较小&#xff0c;如果使用JOIN&#xff0c;那么会shuffle&#xff0c;导致效率变低。广播变量就是将相对…
最新文章