23. 数据结构之位图

news2024/10/1 21:32:17

前言

之前在讲散列表的时候,提到过位图的概念。位图(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.coloradmin.cn/o/686805.html

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

相关文章

SpringBoot的配置环境属性

SpringBoot的配置环境属性 在本文中&#xff0c;我们将讨论SpringBoot的配置环境属性。我们将了解如何使用这些属性来配置我们的应用程序&#xff0c;以便在不同的环境中运行。我们还将了解如何使用SpringBoot的配置文件来管理这些属性。最后&#xff0c;我们将介绍一些最佳实…

java的嵌套类(nested class)、内部类(inner class)的区别

嵌套类即nested class&#xff0c;内部类即Inner class。 概括来说&#xff0c;嵌套类的概念比内部类概念大。嵌套类包含内部类和非内部类。一个内部类一定是一个嵌套类&#xff0c;但一个嵌套类不一定是一个内部类。 在一个类内部或者接口内部声明的类是嵌套类。 下面这些类是…

《Java面向对象程序设计教程》课后编程题

文章目录 第 1 章 Java 概述第 2 章 Java 语言基础第 3 章 Java 面向对象编程第 4 章 Java 图形用户界面程序设计第 5 章 Applet 设计第 6 章 Java 输入输出流与文件操作第 7 章 Java 的多线程机制第 9 章 常用工具类与集合框架 第 1 章 Java 概述 试编写 Java 程序&#xff0…

Android studio新建项目运行遇到的问题

文章目录 The emulator process for AVD xxx has terminated原因&#xff08;环境变量问题&#xff09;解决其他原因 新建的练习项目更改SDK默认位置更改方法 The emulator process for AVD xxx has terminated 运行虚拟机时报此错误 原因&#xff08;环境变量问题&#xff0…

555 timer circuit

#1, Block & principle 1.1&#xff0c; The threshold and trigger levels normally are two- thirds and one-third, respectively, of VCC.(分压&#xff09; 1.2&#xff0c;These levels can be altered by use of the control-voltage terminal. When the trigger in…

生成式 AI:通信服务提供商云转型的下一阶段

【本文由Cloud Ace 整理发布。Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证培…

spring boot引入swagger报错处理

目录 1. 报错说明 2. 查找原因 2.1 此前笔者的代码 3. 问题解决说明 4. 解决方案 4.1 在pom.xml引入springdoc包 4.2 创建配置文件&#xff08;可省略&#xff09; 4.3 在controller加入注解 4.4 查看接口文档 4.5 常用注解 1. 报错说明 在java项目中引入swagger 2.9.2…

Linux设置进程名称(标题) ( 7) -【Linux通信架构系列 】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the…

技术管理三板斧之第一板斧拿结果-定目标

一、现状&#xff1a; 去年年底今年年初&#xff0c;帮助一家公司做了一次大的系统重构&#xff0c;30多小伙伴&#xff0c;经历一次洗礼&#xff0c;对产品定位&#xff0c;技术选型&#xff0c;目标制定&#xff0c;任务分配&#xff0c;协同开发&#xff0c;测试上线&#x…

JS 启动一个计时器来跟踪某一个操作的占用时长

文章目录 需求分析代码 需求 JS 中想要记录一个操作的占用时长 分析 可以启动一个计时器console.time(name:string)来跟踪某一个操作的占用时长。每一个计时器必须拥有唯一的名字&#xff0c;页面中最多能同时运行 10,000 个计时器。 当以此计时器名字为参数调用 console.timeE…

蓝桥杯专题-试题版-【龟兔赛跑预测】【回形取数】【阶乘计算】【矩形面积交】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

计算机的性能指标

计算机的主要性能指标: 机器字长: 指参与运算的基本数位, 它是由加法器, 寄存器的数位决定的, 所以及其资产一般等于内部寄存器的大小 数据路通宽度: 数据总线一次能够并行传送信息的位数, 这里所说的数据通路宽度实际是指外部数据总线的宽度 主存容量: 一个存储器所能存储的…

C语言王国探险记之常量的四大护卫

王国探险记系列 文章目录&#xff08;3&#xff09; 前言 一、常量是什么&#xff1f; 二、常量的第一护卫&#xff1a;字面常量 1.什么是字面常量? 三、常量的第二护卫&#xff1a;const修饰的常变量 1.什么是const修饰的常变量? 2&#xff0c;证明const修饰的常变量…

nginx部署多个前端项目

前端采用vue框架&#xff0c;主要介绍在同一个ipport下&#xff08;或域名&#xff09;&#xff0c;通过访问不同的子路径部署多个项目 把前端打包好的项目直接放进 nginx/html 目录下面&#xff0c; 下面展示根据不同的路由模式的nginx配置&#x1f447; 路由采用hash模式 浏…

基于深度学习YOLOv5电动车头盔佩戴检测设计毕业设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;625头盔 获取完整源码源文件标注好的数据集(在源码文件夹->yolov5-5.0->VOCdevkit)优秀论文答辩PPT及文稿等 设计总说明 在许多非机动车交通事故中&#xff0c;未佩戴头盔是造成驾驶人受伤或死亡的主要原因&#xf…

永磁电机中的磁钢

稀土永磁最大的应用领域就是永磁电机&#xff0c;电机俗称马达&#xff0c;广义上的电机包含了将电能转换成机械能的电动机&#xff0c;和将机械能转换成电能的发电机&#xff0c;不管是电动机还是发电机&#xff0c;都是利用电磁感应定律或电磁力定律作为基础原理的电气设备。…

【教学类-36-02】动物头饰制作(midjounery动物简笔画四图)

作品展示 &#xff08;用midjounery动物简笔画四图作为头饰上的动物&#xff0c;正方形折纸的辅助黏贴物&#xff09; 背景需求&#xff1a; 1、用midjounery生成简笔画动物图案&#xff08;四张预览&#xff09; 2、收集各种不同的动物的一张图片.png 3、设计一款中班幼儿用…

python spider 爬虫 之 解析 xpath 、jsonpath、BeautifulSoup (-)

Xpath 插件下载及安装 下载地址&#xff1a;https://chrome.zzzmh.cn/info/hgimnogjllphhhkhlmebbmlgjoejdpjl 安装xpath 如果下载的xpath后缀是crx 格式的&#xff0c; 直接改成zip格式&#xff0c;然后直接拖拽到上面的界面中便可&#xff0c; 查看是否安装成功&#xff0c…

DOM操作——获取元素的方式

关注“大前端私房菜”微信公众号&#xff0c;回复暗号【面试宝典】即可免费领取107页前端面试题。 DOM-文档对象模型 DOM&#xff08;Document Object Model&#xff09;&#xff1a;文档对象模型 其实就是操作 html 中的标签的一些能力&#xff0c;或者说是一整套操作文档流的…

10年内打造量子超级计算机,行吗?

光子盒研究院 在未来十年内&#xff0c;微软打算建造一台量子超级计算机。 本周四&#xff0c;微软制定了一个开发自己的量子超级计算机的战略蓝图。设计它的团队将花费至少10年的研究时间&#xff0c;来建造一台能够每秒进行一百万次可靠量子操作的机器。 微软声称&#xff0c…