问题:

假设有3亿个整数(范围0-2亿),如何判断某一个树是否存在。局限条件一台机器,内存500m。

常规的思路:我们可以将数据存到一个集合中,然后判断某个数是否存在;或者用一个等长的数组来表示,每个数对应的索引位置,存在就标记为1,不存在0。当然如果设备条件允许,上面的这方案是可行的。

但是现在我们内存只有500m。Int类型4个字节。

单单是这3亿个数都已经:300000000*4/1024/1024 差不多1144m了。显然已经远超过内存限制了。

显然在这种条件下面,我们想要将这些书完整的存储下来已经是不现实的。只有另寻他经。

上面的第二种方案中提供一个很好思路,用标记来标注。我们只有寻求内存占用更小的数据类型来标记了,1int=4byte,1byte = 8bit

如果我们用bit来打标记。就不可以很好的缩小内存占用了嘛。1int=4byte,如果用bit那么内存占用就会缩小32倍,1144/32大概36m就可以了。

也就是说其实我们的一个int位置,可以表示32个数据存在与否。

Int 1的二进制完整表示:0000 0000 0000 0000 0000 0000 0000 0001,它是有32位,只不过平时前面的0都是没有显示的

我们声明一个int类型的标记数组:flag[2亿/32 +1],数组长度2亿/32 +1,就可以了,为什么是2亿/32,不是3亿呢,因为这3亿个数的范围就是0-2亿,所以我们的标记数组最大只需要对应2亿即可。有的数,就将其标记为1。就是说数组的没一个元素其实包含了32个数存在与否。

Flag[0] --->0-31

Flag[1] --->32-63

Flag[2] --->64-95

Flag[3] --->96-127

.......

我们怎么来操作这个int类型里面的32位呢,这就要用到我们的位运算:

<<:左移

>>:右移

&:同位上的两个数都是1则位1,否则为0

|:同为上的两个数只要有一个为1 则为1,否则为0

我们怎么找到某个数的标记在数组的索引及其值的多少位呢?

假设3这个数

Index = 3 / 32 = 0

Location = 3 % 32 = 3

3的标记应该在flag[0] 的第三个位置,将其置为1:

flag[0]  |=  1 << Location (位或是不会影响其他位置1的标记)

原本的flag[0]  0000 0000 0000 0000 0000 0000 0000 0011    原本已经有了0和1

     |    0000 0000 0000 0000 0000 0000 0000 000    1 << Location 之后的

得到:           0000 0000 0000 0000 0000 0000 0000 011    现在有0,1,3了

这样就可以所有的数据标记给保存下来了。

那么判断的时候怎么判断呢。

同理先找到某个数精确位置

还是3

Index = 3 / 32 = 0

Location = 3 % 32 = 3

现在我们的flag[0] -> 0000 0000 0000 0000 0000 0000 0000 1011

Exist = flag[0] & (1 << Location) != 0  &:相同位上的两个数都是1则位1,否则为0

  0000 0000 0000 0000 0000 0000 0000 1011

&  0000 0000 0000 0000 0000 0000 0000 1000

   0000 0000 0000 0000 0000 0000 0000 1000

&运算之后结果不为0说明该数存在。

大致的思路就是这个样子,下面用代码实现上述逻辑

package com.nijunyang.algorithm.math;

/**
* Description:
* Created by nijunyang on 2020/5/5 22:33
*/
public class BitMap {
/**
* 范围中最大的那个数
*/
private int max; private int[] flag; public BitMap(int max) {
this.max = max;
flag = new int[max >> 5 + 1]; //除以32也可以表示为>>5
} /**
* 添加数据标记
* @param val
*/
public void put(int val) { //往bitmap里面添加数字 int index = val / 32; // 计算数组索引位置
int location = val % 32; // 计算在32位int中的位置
flag[index] |= 1 << location; //标记位改成1
} /**
* 判断是否存在
* @param val
* @return
*/
public boolean exist(int val) {
int index = val / 32;
int location = val % 32; int result = flag[index] & (1 << location);
return result != 0;
} /**
* 移除标记
* @param val
* @return
*/
public void remove(int val) {
int index = val / 32;
int location = val % 32; System.out.println(Integer.toBinaryString(flag[index]));
flag[index] = flag[index] &~ (1 << location); //~取反1变0 0变1
System.out.println(Integer.toBinaryString(flag[index]));
} public static void main(String[] args) {
BitMap bitMap = new BitMap(200_000_000);
bitMap.put(128);
bitMap.put(129);
System.out.println(bitMap.exist(127));
System.out.println(bitMap.exist(128));
bitMap.remove(128);
System.out.println(bitMap.exist(128)); }
}

虽然说用bitMap的这种思想可以解决上面的这个问题,但是它还是有缺点的,我们上面用的数字,如果是其他类型可能就需要hash之后得到hashcode再进行这个操作,对于hash冲突是无法解决的。因为标记只有0和1。数据量少的时候相对于普通的hash集合操作并没有优势。它对于那种数据量很大,且数据相对密集的,因为数组的长度是和最大的数据值有关,而不是和集合容量有关。

布隆过滤器就使用bitMap的思想。不过它同时会使用集中hash算法来计算hashcode,来尽量解决hash冲突。Redis缓存设计与性能优化 中有简单的介绍。

JDK中也有一个BitSet类。

算法---BitMap的更多相关文章

  1. 海量数据处理算法—Bit-Map

    原文:http://blog.csdn.net/hguisu/article/details/7880288 1. Bit Map算法简介 来自于<编程珠玑>.所谓的Bit-map就是用一 ...

  2. 海量数据处理算法—BitMap

    1. Bit Map算法简介 来自于<编程珠玑>.所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素.由于采用了Bit为单位来存储数据,因此在存储空 ...

  3. 从一道高大上的面试题来学习位图算法BitMap

    今天我偶然刷到了一篇文章,"华为二面:一个文件里面有5亿个数据,一行一个,没有重复的,进行排序".不知道又是哪个无良媒体瞎起的标题,夺人眼球. 不过说归说,这题听着就很高大上,5亿 ...

  4. [Android算法] bitmap 将图片压缩到指定的大小

    Bitmap压缩到指定大小: private void imageZoom() {//图片允许最大空间 单位:KBdouble maxSize =400.00;//将bitmap放至数组中,意在bit ...

  5. 复选框与bitmap算法实践

    bitmap(位图)算法 bitmap算法是利用数据二进制的每一位的值来表示数据的算法,可用来压缩保存数据集. 如何保存 如 5(int)的二进制表示为 101b,第一位和第三位的值是1就可以表示数据 ...

  6. Android图片缓存之Bitmap详解

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. 图片缓存相关博客地址: Android图片缓 ...

  7. 大数据分析常用去重算法分析『Bitmap 篇』

    大数据分析常用去重算法分析『Bitmap 篇』  mp.weixin.qq.com 去重分析在企业日常分析中的使用频率非常高,如何在大数据场景下快速地进行去重分析一直是一大难点.在近期的 Apache ...

  8. 大数据计算:如何仅用1.5KB内存为十亿对象计数

    大数据计算:如何仅用1.5KB内存为十亿对象计数  Big Data Counting: How To Count A Billion Distinct Objects Using Only 1.5K ...

  9. Flink去重统计-基于自定义布隆过滤器

    一.背景说明 在Flink中对流数据进行去重计算是常有操作,如流量域对独立访客之类的统计,去重思路一般有三个: 基于Hashset来实现去重 数据存在内存,容量小,服务重启会丢失. 使用状态编程Val ...

随机推荐

  1. 数字电路技术之触发器(基本RS触发器)

    一.触发器的知识 1.触发器是构成时序逻辑电路的基本逻辑部件. 2.[1]它有两个稳定的状态:0状态和1状态:      [2]在不同的输入情况下,它可以被置成0状态或1状态:      [3]当输入 ...

  2. STL迭代器的使用

    STL的迭代器听起来怪吓人的,其实并不是什么高深的东西,说白了就是定义了一个指向STL的指针.. 对于没个STIL都可以定义 set,,vector ,,map,,,string 定义: set< ...

  3. Redisson 实现分布式锁的原理分析

    写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题.​ 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁, ...

  4. PHP函数:func_num_args

    func_num_args()  - 返回传递给函数的参数数量. 说明: func_num_args ( void ) : int 参数: 无 返回值: 返回传入当前用户定义函数的参数数量. 参考链接 ...

  5. 成员指针与mem_fn

    本文是<functional>系列的第4篇. 成员指针是一个非常具有C++特色的功能.更低级的语言(如C)没有类,也就没有成员的概念:更高级的语言(如Java)没有指针,即使有也不会有成员 ...

  6. golang方法详解

    Go 语言 类型方法是一种对类型行为的封装 .Go 语言的方法非常纯粹, 可以看作特殊类型的函数,其显式地将对象实例或指针作为函数的第一个参数,并且参数可以自己指定,而不强制要求一定是 this或se ...

  7. samba 客户端工具 smbclient和samba挂载到本地

    smbclient命令属于samba套件,它提供一种命令行使用交互式方式访问samba服务器的共享资源. 安装 yum install -y samba-client 常用参数 -c<命令> ...

  8. 不使用tomcat,仅适用javaSE手写服务器--模拟登陆

    1.搭建框架 我们只是简单模拟,框架简单分三个模块 a,服务器端server包 b,servlet,根据不同的请求url,利用反射生产对应的servlet c,IO工具包,用来关闭IO流 d,编写we ...

  9. xhprof windows下安装和使用(转载)

    1.使用5.3.3以上的php版本,或者直接下载wamp2.1集成环境. 2.下载xhprof for windows版本,地址:http://www.benjamin-carl.de/?downlo ...

  10. ApiPost如何在预执行脚本里添加请求参数?

    ApiPost V3引入了预执行脚本和后执行脚本的概念,详细可以通过链接:<ApiPost的预执行脚本和后执行脚本>了解学习更多.本文主要介绍如何在预执行脚本里增加请求参数. 使用场景 我 ...