23. 数据结构之位图

news/2024/10/18 1:38:47/

前言

之前在讲散列表的时候,提到过位图的概念。位图(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 {@Testpublic 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;//如果算出来查找元素下标大于数组长度,一定不在数组中,返回falseif (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.ppmy.cn/news/581102.html

相关文章

eyoumailserver邮箱服务器与foxmail 邮箱客户端的使用和安装

友情链接&#xff1a;Java实现邮箱发送验证码、代码示例【qq邮箱为例】 Java实现邮箱验发送证码、代码示例【qq邮箱】_苏凯的博客-CSDN博客_java实现邮箱验证代码 2 邮箱服务器 2.1 邮箱服务器的基本概念 邮件的客户端&#xff1a;可以只安装在电脑上的也可以是网页形式的 …

mail服务配置

一、配置Linux2为Mail服务器&#xff0c;安装postfix&dovecot. 事实上目前已经没有人会用ip来寄信了&#xff0c;我们通常接收到的email都是使用【账户主机名称】的方式来处理的。 对于一般的服务器来说&#xff0c;我们只要使用正解让客户端可以正确找到我们服务器的IP即…

企业邮箱邮件的服务器地址是什么?企业邮箱服务器出错怎么办?

邮箱邮件的服务器地址是什么&#xff1f;以TOM企业邮箱为例&#xff0c;企业邮箱的服务器一般分为两种&#xff0c;发信服务器协议一般都是用smtp协议&#xff0c;收信服务器协议一般是用imap/pop3协议&#xff0c;不同服务器协议规则也不一样&#xff0c;无论设置邮箱手机客户…

邮件服务解决方案--EwoMail

最近在找开源的邮件系统&#xff0c;之前采用的是Centospostfixdovecotextamil搭建邮件服务环境&#xff0c;配置起来稍显麻烦。 无意间发现ewomail&#xff0c;看简介是一键部署&#xff0c;架构和上面的类似。 Postfix&#xff1a;邮件服务器 Dovecot&#xff1a;IMAP/POP3…

邮件服务器如何搭建?企业邮箱邮件服务器搭建只需几步即可

企业邮箱作为公司的办公工具&#xff0c;其邮件服务器的搭建&#xff0c;决定了邮件收发的稳定性和安全性&#xff0c;今天就给大家分享下企业邮箱邮件服务器搭建的方法&#xff0c;既可以节省成本也能提高邮箱性能。 一个顶级域名&#xff1a; 什么是企业邮箱&#xff0c;企…

hMailServer搭建企业邮箱服务器

1、下载hMailServer安装包&#xff1a;点击打开链接 2、安装hMailServer 参考文档&#xff1a;点击打开链接&#xff08;配置mysql 提示缺少“libmysql.dll”动态链接库文件。该文件可从mysql的源码包或安装后的“mysql\lib\”文件夹下拷贝&#xff08;必须是win32版本的mysq…

[基础服务] 常用邮箱服务地址

简介 我们通常用SMTP (25) 发送邮件&#xff0c; pop3 (110) 接收邮件, IMAP4 (143)远程访问读取邮件。它们都隶属于TCP/IP协议簇&#xff0c;默认状态下&#xff0c;分别通过TCP端口25、110和143建立连接。 详情 谷歌邮箱 (mail.google.com) : POP3服务器地址: pop.gmail.co…