<BitMap>大名鼎鼎的bitmap算法
BitMap
抛砖引玉
首先,我们思考一个问题:如何在3亿个整数(0~2亿)中判断某一个数是否存在?现在只有一台机器,内存只有500M
这个问题像不像我们之前提到过的一个在0-10个数中,判断某一个数是否存在的问题呢?当时我们采取的做法是,建立一个长度是11的数组,下标从0开始,如果1存在则data[1] = 1,数字作为数组的下标,若该数字存在则在data[数字] = 1,将其赋值为1。那么我们这个是否可以这么做呢?
明显不行。为什么呢?因为我们如果判断2亿个数字是否存在,建立一个2亿长度的数组,疯了么?想想就知道不可能。那么我们应该怎么做呢?
这时候我们就需要变换我们的思路了,一个int类型整型,我们占用4个字节,一个字节我们占用8位:1 byte = 8 bit4 byte = 32 bit那么,我们如果将这2亿个数据不按照int的形式存储,而是按照位的形式存储,我们原来存储一个数字(4个字节)现在不就是可以存储32个数字(4个字节 = 32位)了么。秒啊~
什么是BitMap
位图(BitMap),又称作栅格图或者点阵图,是使用像素阵列来表示的图像---------来自百度百科。
明显上面说的什么,我们也不清楚,反正很高深了就是,其实我觉得简单理解就是我们用位(bit)来操作数据,以此来操作大数据量
至于我们为什么用位来操作呢?因为计算机最小的单位就是bit(位)
类型的基础讲解
常用的数据类型和位的转换
1 byte = 8 bit
1 int = 4 byte = 32 bit
1 float = 4 byte = 32 bit
1 long = 8 byte = 64 bit
1 char = 2 byte = 16 bit
我们这里就不做详细的解释,只要记住:1 byte = 8 bit,即1字节 = 8位
常用的位操作运算符
我们这里讲述0. 常用的6种常用的位运算符(1)按位或(2)按位与(3)按位异或(4)求反(5)左移运算(6)右移运算
按位或运算
按位或运算符,记做“|”,是一个双目运算符。
简单来说就是二进制的位中只要有一个是1,其计算结果就是1,否则就是0
举例说明:
11 | 7
11 0000 0000 0000 0000 0000 0000 0000 1011 | 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 1111 11 | 7 = 1011 | 0111 = 1111 = 15
按位与运算
按位与运算符,记做“&”,是一个双目运算符。
简单来说就是二进制的位中只有都是1,才会是1,否则就是0
举例说明:11 & 7
11 0000 0000 0000 0000 0000 0000 0000 1011 & 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 0011 11 &7 = 1011 | 0111 = 0011 = 3
按位异或运算
按位异或运算符,记做“^”,是一个双目运算符。
简单来说就是二进制的位中只要两个数字不一样就是1,否则就是0
举例说明:
11 ^ 7
11 0000 0000 0000 0000 0000 0000 0000 1011 ^ 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 1100 11 &7 = 1011 | 0111 = 1100 = 12
按位异或运算
public class Main1 { public static void main(String[] args) {
System.out.println("11 | 7 的结果是 : "+(11|7));
System.out.println("11 & 7 的结果是 :"+(11&7));
System.out.println("11 ^ 7 的结果是 :"+(11^7));
}
}
//----------------------------输出如下------------------------------
11 | 7 的结果是 : 15
11 & 7 的结果是 :3
11 ^ 7 的结果是 :12
按位求反运算
按位求反运算符,记做“~”,是一个单目运算符。
简单来说就是二进制的位中,原来是1,结果就是0,原来是0,结果就是1
左移运算
左移运算符“<<”是一个双目运算符,其功能就是把"<<"左边的运算数的各个二进位全部左移若干位。
举例说明:5 << 4
5 0000 0000 0000 0000 0000 0000 0000 0101 <<4 结果 0000 0000 0000 0000 0000 0000 0101 000 5 << 4 = 0101 << 4 = 0101 0000 = 5* 2^4 = 5*16 =80
右移运算
右移运算符 ">>" 是一个双目运算符,其功能就是把">>"左边的运算数的各个二进位全部右移若干位。
举例说明:16 >> 3
16 0000 0000 0000 0000 0000 0000 0001 0000 >>3 结果 0000 0000 0000 0000 0000 0000 0000 0010 16 >> 3 = 0001 0000 >> 3 = 0010 = 16 / 2^3 = 16/8 =2
如何巧妙的运用
a >> x
这个就可以记做是:a / 2 ^ x比如说:8 >> 2 = 8 / 2 ^ 2 = 8 / 4 = 2
a << x
这个就可以记做是: a * 2 ^ x比如说: 8 << 2 = 8 * 2 ^ 2 = 8 * 4 = 32
a % 2^n
这个就可以记做是:a & 2^n-1比如说:7 % 4 = 7 & (4-1) = 3
实际问题分析
问题描述
现在我们假设我们有64个数字,最大的数字就是64,那么我们用Bitmap的思想应该怎么解决呢?
分析
首先我们这里用int的数组来进行存储,一个int是4个字节,1个字节是8位,因此一个int型的数字可以存32位
我们这里最大的数是64
数组 | 存储位数 | 能存多少 |
---|---|---|
data[0] | 0-31 | 32位 |
data[1] | 32-63 | 32位 |
data[2] | 64-95 | 32位 |
由此可以推到出来:我们存取n个数字,其中最大的数字是MAX,则需要data[MAX/32 + 1]长度的数组,这里的32是因为1 int = 4 字节 = 32位
假设我们需要判断64是否在列表中(以int数组存储),我们就应该这样来计算
第一步我们判断声明多大的int型的数组
因为我们最大的数是64,所以根据我们的公式:data[MAX/32 + 1] ,由此可以计算出,我们声明的数组长度是data[64/32 + 1] = data[3],也就是说我们应该声明数组长度是3,即data[0] data[1] data[2]
先定位到数组,判断是第几个数组中存储着
因为我们一个数组长度是1个int,就是32位,查询的数字是64,所以64 / 32 = 2,因此定位到了data[2]
这里除以32是因为,我们以int数组为例,1 int = 32 bit
再定位这个元素是数组中的第几位
同理,我们一个数组的长度是一个int,也就是4个字节,32位,查询的数字是64,64 % 32 = 0,因此定位到了我们是存在data[2]数组中的第0位
这里取余32是因为,我们是以int数组为例,1 int = 32 bit
此时我们只需要判断data[2]数组中的第0位是否为1,为1表示已经存在,否则就是不存在。
这就是BitMap的算法的核心思想
公式小结
我们以int数组为例,一个int占用4个字节,也就是32位,存储数据的最大值是MAX(比如存1-2000,最大值就是2000)
判断总共需要多大的int数组 :
MAX/32 + 1
判断当前这个数字n在第几个数组中 :
n / 32
判断当前这个数字n在数组中的第几位上 :
n % 32
我们上面的32就是int占用的位数,可以换成其他的类型,如果用byte数组,则将32换成8,因为1个byte是一个字节,是8位。
计算存储空间
假设我们现在要存储2亿个数字,如果直接用int数组来存,一个int是一个数字,则需要:2亿 * 4字节 / 1024 / 1024 = 762M
假设我们现在用BitMap的思想存储,也是使用int数组,只不过一个int存储32个数组,则需要:2亿 / 32 + 1(需要开的int数组) * 4字节 / 1024 / 1024 = 23M
看看,是不是空间就是这么省下来了。
实战举例
题目
假设我们有序列 2 9 33 12 11 65 14 , 我们开一个int类型的数组,将其存储进去,然后判断其是否存在,并可以实现某一个数字的删除,并用位运算符实现。
分析
一: 首先我们通过上面的三个公式已经可以很快的知道数组定义多大,在哪个数组中放值(哪一个数组,数组中的哪一位),但是这里我们的要求是使用位运算符,这时候结合我们最开始讲的位运算符将其进行简单的转换
判断数组定义多大
a / 2^n = a >> ndata[MAX/32 + 1] = data[MAX / 2 ^ 5 + 1] = data[MAX >> 5 + 1]
数字n存在哪一个数组中
a / 2^n = a >> ndata[n/32] = data[n / 2 ^ 5] = data[n >> 5]
数字n在数组的哪一位
a % 2^n = a & 2^n - 1data[n%32] = data[n % 2 ^ 5] = data[n & (2 ^ 5 -1)] = data[n & 31]
二: 其次我们在存入数组中需要将数组的第loc位由0变为1
将下标为loc的位由0变为1,可以把1左移loc位,然后使用或操作(只要有1个为1就是1)
所以新增元素公式就是: data[X] = data[X] | (1 << loc),loc为当前数组的哪一位,data[X]就是当前的数组
三: 下一个问题就是我们需要查找一个数字是否已经存在
这时候我们就可以考虑&运算符,即都是1才是1,否则就是0判断下标loc的位是否已经存在,可以把1左移loc位,然后做与操作(都为1才是1,否则是0)
如下图,判断loc位上是否存在:(下图表示已经存在)
如下图,判断loc位上是否存在:(下图表示不存在)
所以查找元素公式就是:0 == data[X] & 1 << loc,loc为当前数组的哪一位 ,这时候用0判断是因为我们除了移位过去的那位上的数字是1,其他位上的数字都是0,所以如果最后结果是0,表示就是不存在,否则就是存在
四: 最后一个问题就是我们要删除一个数字呢?就是将这个数字由1变为0(这里我们假设删除的数字是肯定存在的,不允许做不存在就删除)
这时候考虑我们异或操作:相同则为0,不同则为1,那如果我当前位上存在则肯定是1,如果我拿一个当前位是1的数和其做异或操作不就可以了假设我们需要删除下标是lco上的数我们的异或操作是相同为0,不同为1,所以即使数组原来位置上有1,我们也不用害怕,因为原来位子上是1,但是我们1进行左移loc后,除了loc上的数是1,其他位上的数肯定是0,参考上面的图解
所以删除元素公式:data[X] = data[X] ^ (1 << loc),loc为当前数组的哪一位
代码实现
package com.demo.bitsmap;
public class Main2BitsMap { private int[] vals; public Main2BitsMap(int size) {
this.vals = new int [size];
} public static void main(String[] args) {
int [] t = {1,2,3,4,5,33,34,45,77,108};
Main2BitsMap map = new Main2BitsMap((108>>5)+1);
for (int i :t) {
map.add(i);
}
map.print();
map.printBirany();
System.out.println("添加数据109");
map.add(109);
System.out.println("是否包含 77 这个数据 ? " +map.find(77));
System.out.println("是否包含 109 这个数据 ? " +map.find(109));
System.out.println("删除数据77");
map.delete(77);
System.out.println("是否包含 77 这个数据 ? " +map.find(77));
} public void add(int t) { int index = t>>5; int loc = t&31; vals[index] = vals[index] | 1 << loc;
} public boolean find(int t) {
int index = t>>5; int loc = t&31; int result = (vals[index]>>loc ) &1; return result ==0 ?false:true;
}
public boolean delete(int t ) {
int index = t>>5; int loc = t&31; if(!find(t)) {
return false;
}
vals[index] = vals[index] ^ (1<<loc); return true; } public void print() {
for (int i : vals) {
System.out.print(i+" ");
}
} public void printBirany() {
int index =0;
System.out.println();
for (int i : vals) {
for (int j = 31; j >=0; j--) {
System.out.print((i>>j)&1 );
index++;
if(index==5) {
System.out.print(" ");
index=0;
} }
index =0;
System.out.println(); }
}
}
结果显示
62 8198 8192 4096
00000 00000 00000 00000 00000 01111 10
00000 00000 00000 00010 00000 00001 10
00000 00000 00000 00010 00000 00000 00
00000 00000 00000 00001 00000 00000 00
添加数据109
是否包含 77 这个数据 ? true
是否包含 109 这个数据 ? true
删除数据77
是否包含 77 这个数据 ? false
总结
BitMap的时间复杂度是O(1)
BitMap的优点(1)可以解决数据判重的问题(2)可以对没有重复的数据进行排序(3)存储巧妙,节约空间,效率高
BitMap的缺点(1)数据不允许重复。因为只有0和1没有其他了(2)数据量少的时候相对于普通的hash没有优势(因为存储数据量小,我们直接用正常的map或者其他数据结构存储就行)
(3)无法处理字符串(4)无法解决Hash冲突(因此我们的前提条件就是数据不重复)
<BitMap>大名鼎鼎的bitmap算法的更多相关文章
- BitmapUtil【缩放bitmap以及将bitmap保存成图片到SD卡中】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 用于缩放bitmap以及将bitmap保存成图片到SD卡中 效果图 代码分析 bitmapZoomByHeight(Bitmap s ...
- 关于bitmap recycle trying to use a recycled bitmap android.graphics.Bitmap
在开发中,一直使用4.0以上手机作为測试机所以一直没有出现这个问题,今天换了2.3版本号的手机.出现了这个错误: trying to use a recycled bitmap android.gra ...
- Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXX
近期在做和图片相关显示的出现了一个问题,整理一下思路.分享出来给大家參考一下: Exception Type:java.lang.RuntimeException java.lang.RuntimeE ...
- 浅谈Android下的Bitmap之大Bitmap加载
引言 我们常常提到的“Android程序优化”,通常指的是性能和内存的优化,即:更快的响应速度,更低的内存占用.Android程序的性能和内存问题,大部分都和图片紧密相关,而图片的加载在很多情况下很用 ...
- java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@412d7230
近期遇到了如标题这种错误,再次记录解决方法.本文參考帖子: http://bbs.csdn.net/topics/390196217 出现此bug的原因是在内存回收上.里面用Bitamp的代码为: t ...
- Flash 矢量图和位图性能对比 导出为位图/缓存为位图 export as bitmap / cache as bitmap
大家都知道Flash处理矢量图比位图要慢,而具体的性能上对比也有不少的前人已经做过.http://bbs.9ria.com/forum.php?mod=viewthread&tid=2282 ...
- c# bitmap和new bitmap(bitmap)及在System.Drawing.Image.get_RawFormat()报错“参数无效”
问题情境: 给picturebox赋image属性,我用一下代码,出错: Bitmap theBitmap = convertCameraData.display(rawDataArray, heig ...
- Android java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@412d7230
近期遇到了如标题这种错误,再次记录解决方法.本文參考帖子: http://bbs.csdn.net/topics/390196217 出现此bug的原因是在内存回收上.里面用Bitamp的代码为: t ...
- BitMap算法详解
所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间. 基本思想: 这此我用一个简单的例子来详细 ...
随机推荐
- 【Fine学习笔记】python 文件l操作方法整理
python脚本可以对excel进行创建.读.写.保存成指定文件名,保存到指定路径的操作.整理了以下处理方法: 首先区别几个操作方式: "r" 以读方式打开,只能读文件 , 如 ...
- 第二章linux网络基础设置总结!
一:查看及测试网络 (1)查看活动的网络接头命令:ifconfig (2)查看所有网络接口命令:ifconfig -a (3)查看指定的网络接口(不论该网络接口是否处于激活状态)命令:ifconfig ...
- 开关机安全控制!(设置进入bois的密码)
1.调整 BOIS 引导设置(1)将第一引导设备设为当前系统所在硬盘 (2)设置管理员密码 (3)进入bois后如图所示需输入bols密码才能登入
- Website's Game source code
A Darkroom by doublespeakgames <!DOCTYPE html> <html itemscope itemtype="https://schem ...
- 【摘录自MDN】对事件冒泡和捕捉的解释
当一个事件触发了一个有父元素的元素(例如我们的<video>时),现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段. 在捕获阶段: 浏览器检查元素的最外层祖先(<html> ...
- 计算机网络 - TCP/IP模型
图片来自网上资料
- js判断对象中是否存在某一项和判断是否是对象
1.判断是否为对象 let str = { name: '第一', age: 12 } console.log(typeof str== "object") 2.判断对象中是否有某 ...
- vue element 时间选择器设置禁用日期
在 el-date-picker 组件中有一个 picker-options 属性 disabledDate 可以设置日期的可选范围 <el-date-picker v-model=" ...
- burpsuite使用--暴力破解
测试靶机:dvwa 浏览器开启代理,使用burpsuite拦截: 并将拦截到的内容发送到intruder进行暴力破解 右边的Add$和Clear$都是选择爆破范围的操作,一个是选择,一个是清除,这里只 ...
- SQL SERVER 语法汇总
一.基础 1.说明:创建数据库CREATE DATABASE database-name 2.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备 ...