前言:

之前碰到过一道面试题,大概内容如下

有40亿个无符号的整型数据,现在给定一个目标数字,判断这个数字是否在这40亿数据中?  

刚开始想的时候,处理思路应该很简单,直接把这40亿个数字存储到一个集合中,但是仔细一算,发现并不现实,一个int类型的数字占4个字节,4byte * 40亿 差不多就是16G的大小,占用内存太大,那么还有没有好的办法给解决呢?

1.解决思路

一个int类型数字占用4字节,32bit位,那我们能不能用一个bit位来表示一个数字呢?

理论上肯定是可以的,如下图所示:

我们用bit0位代表1,一直到31位bit代表数字32,如果当前bit位值为1,则说明存在对应数字。这样的话,一个32bit的int,可以存储32个数字,如果按照这种模型的话,那么上面的40亿个数字使用500多M的空间就可以存放下来。

假如现在需要存放数字1、3、31,那么具体如下

是一个不错的思路,但是上面这种,超过32bit位的数字应该怎么办呢?总不能无限扩bit位吧?

我们可以使用数组来解决这个问题,32bit所组成的数组,如下所示:

我们使用这个数组来存放更多的值,理论上来说,无论多少的值(N个数字),只要内存放的下,我们就可以使用int[]来存放,数组的长度等于N/32+1

2.代码实现

// 假设我们有10亿个数据要处理
private int N = 1000000000;
// 在这里使用int[]数组来存储,按照上面的分析,数组长度为N / 32 + 1
private int[] a = new int[N / 32 + 1];public void addValue(int n) {// n >> 5 等于 n/32,算出n所在int数组的那一列int row = n >> 5;//相当于 n % 32 求n在数组a[i]中的下标// 这句代码可以拆成三句来看// 1.n & 0x1F 即n 和 00001111的与操作,获得n在当前32bit位中的下标index// 2.1<<index 即获取对应下标的值,当对应index 的bit位值为1时,对应的数字即1<<index// 3.a[row] |= x 即 a[row] = a[row] | x,针对已存在的a[row]值,或上新的x值,即可获得新值a[row] |= 1 << (n & 0x1F);
}// 判断所在的bit为是否为1
public boolean exists(int n) {int row = n >> 5;return (a[row] & (1 << (n & 0x1F))) == 1;
}public static void main(String[] args) {BitSetTest bitSetTest = new BitSetTest();bitSetTest.addValue(31);
}

获取的话,主要分为两步,第一步:获取数字n在int[]中的row(具体某行);第二步:获取数字n在int[row]中具体的index

3.java中的BitSet实现

在java.util包下,有一个叫做BitSet的类,它本质上就是上述位图的实现,我们先来看下其是如何使用的

// 创建一个位图对象
BitSet bitSet = new BitSet();
// 向里添加10W个数字
for (int i = 0; i < 100000; i++) {bitSet.set(i);
}
// 判断9999是否在位图中
boolean b = bitSet.get(9999);// 返回true 

使用起来非常简单,就我们在前言中提到的问题,就可以用同样的方式来解决,我们可以将这40亿个数字都调用bitSet.set()方法添加进来(如果觉得太多,可以分批次添加),最终调用get方法来进行指定数字查询即可。

3.1 BitSet的构造方法

public class BitSet implements Cloneable, java.io.Serializable {// 当计算给定数字属于long[]的哪一行时,需要ADDRESS_BITS_PER_WORD,类似于上面使用int[]时,值为5private final static int ADDRESS_BITS_PER_WORD = 6;// BITS_PER_WORD=64,代表Long类型有64个bit位private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;// 以long[]数组来存储数字private long[] words;// 当前long[]已经用到哪一行private transient int wordsInUse = 0;// 提供两个构造方法,默认数据为64,也就是一个long的bit位数public BitSet() {initWords(BITS_PER_WORD);sizeIsSticky = false;}public BitSet(int nbits) {// nbits can't be negative; size 0 is OKif (nbits < 0)throw new NegativeArraySizeException("nbits < 0: " + nbits);initWords(nbits);sizeIsSticky = true;}// 初始化long[]数组大小private void initWords(int nbits) {words = new long[wordIndex(nbits-1) + 1];}private static int wordIndex(int bitIndex) {return bitIndex >> ADDRESS_BITS_PER_WORD;}
}

看了BitSet的构造方法,与上面我们的解决方案有所不同的是,这里采用long[]数组来存放数据,其他没有不同。

默认的话,最大为64,所以对应的long[]数组大小为64 >> 6 = 1。当然,也可以在创建BitSet的时候指定数据大小,这时候数组大小即为(n-1) >> 6 +1

3.2 BitSet.set() 添加数据

public class BitSet implements Cloneable, java.io.Serializable {public void set(int bitIndex) {if (bitIndex < 0)throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);// 获取当前数据所属行int wordIndex = wordIndex(bitIndex);// 如果计算出的行超过目前的最大行数,则扩容long[]一倍expandTo(wordIndex);// 设置对应index值为 1<< bitIndexwords[wordIndex] |= (1L << bitIndex); // Restores invariantscheckInvariants();}private void expandTo(int wordIndex) {int wordsRequired = wordIndex+1;if (wordsInUse < wordsRequired) {// 扩容到合适的行数ensureCapacity(wordsRequired);wordsInUse = wordsRequired;}}private void ensureCapacity(int wordsRequired) {if (words.length < wordsRequired) {// 扩容到最大int request = Math.max(2 * words.length, wordsRequired);words = Arrays.copyOf(words, request);sizeIsSticky = false;}}
}

使用BitSet添加数据时候,代码并不复杂,基本与上2中的操作是一致的,只不过这里多一个自动扩容的操作,防止数据过大,当前long[]存储不下

3.3 BitSet.get() 查询数据是否在BitSet中

public class BitSet implements Cloneable, java.io.Serializable {public boolean get(int bitIndex) {if (bitIndex < 0)throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);checkInvariants();// 先获取数字所在long[]的行int wordIndex = wordIndex(bitIndex);// 比较对应index的值(0或1),是否匹配,若能匹配上,则说明该值在BitSet中return (wordIndex < wordsInUse)&& ((words[wordIndex] & (1L << bitIndex)) != 0);}
}

查询数据是否存在的过程,就是按照set的过程进行一遍查询而已,不算复杂,主要就是寻找对应行的对应位所对应的值是否为1,为1说明数据存在,否则,数据不存在

BitSet还提供了很多其他的方法,大家可以自行阅读测试,笔者不再赘述。

参考:

位图的原理和简单实现 - 小小小小的我 - 博客园

位图的实战场景及源码分析相关推荐

  1. React Native Android入门实战及深入源码分析系列(1)——Hello world

    转载需注明出处:http://blog.csdn.net/minimicall?viewmode=contents 从今天起,我要分析React Native for Android的源码. 本系列主 ...

  2. 虚幻4场景渲染源码分析概述

    大学时候跟舍友两个人搞的,可能也没啥用,先存在这吧,基本就是对官网的一个翻译. 主要类关系及函数流程 主要类关系 主要类关系图: 说明:FSceneRenderer类包含了FScene类.FMeshE ...

  3. JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标

    JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...

  4. 《MapReduce 2.0源码分析与编程实战》一第1章 HBase介绍

    本节书摘来异步社区<MapReduce 2.0源码分析与编程实战>一书中的第1章,作者: 王晓华 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  5. 分布式事务 TCC-Transaction 源码分析 —— 项目实战

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/TCC-Transaction/http-sample/ 「芋道 ...

  6. 详细讲解:RocketMQ的限时订单实战与RocketMQ的源码分析!

    目录 一.限时订单实战 1.1.什么是限时订单 1.2.如何实现限时订单 1.2.1.限时订单的流程 1.2.2.限时订单实现的关键 1.2.3.轮询数据库? 1.2.4.Java 本身的提供的解决方 ...

  7. SpringCloudAlibaba注册中心与配置中心之利器Nacos实战与源码分析(下)

    源码资料 文档资料 <<Nacos架构与原理>>书籍于2021.12.21发布,并在Nacos官方网站非常Nice的提供其电子书的下载.我们学习Nacos源码更多是要吸取其优秀 ...

  8. Spark 随机森林算法原理、源码分析及案例实战

    图 1. Spark 与其它大数据处理工具的活跃程度比较 回页首 环境要求 操作系统:Linux,本文采用的 Ubuntu 10.04,大家可以根据自己的喜好使用自己擅长的 Linux 发行版 Jav ...

  9. 从flink-example分析flink组件(3)WordCount 流式实战及源码分析

    前面介绍了批量处理的WorkCount是如何执行的 <从flink-example分析flink组件(1)WordCount batch实战及源码分析> <从flink-exampl ...

  10. zookeeper服务发现实战及原理--spring-cloud-zookeeper源码分析

    1.为什么要服务发现? 服务实例的网络位置都是动态分配的.由于扩展.失败和升级,服务实例会经常动态改变,因此,客户端代码需要使用更加复杂的服务发现机制. 2.常见的服务发现开源组件 etcd-用于共享 ...

最新文章

  1. Python网络爬虫之scrapy爬虫的基本使用
  2. 美国12大科技公司如何参与自动驾驶?
  3. 《linux核心应用命令速查》连载十四:fuser:用文件或者套接口表示进程
  4. J2SE的30个基本概念
  5. 使用Filter跟踪Asp.net MVC页面加载(转)
  6. ASP.NET制作一个简单的等待窗口
  7. 13 种 JavaScript 代码技巧
  8. 不要轻易和少妇上床:金融危机是这…
  9. Java项目几个月能完成_c#项目转JAVA,第5个月,基本完成
  10. 软件测试黑马程序员课后答案_软件测试教程课后答案
  11. cordova 支付_Cordova打包教程(H5项目打包成ipa)
  12. 使用MATLAB分析处理csv数据
  13. Nginx安装出现错误解决记录
  14. WORD中如何转换符号的全角半角
  15. 自然语言处理(NLP)资源
  16. python爬虫:爬取链家深圳全部二手房的详细信息
  17. 超级计算机 弦理论,暗能量导致宇宙加速膨胀?弦理论能否帮忙解决?
  18. IE 调试工具 IETester+DebugBar
  19. 极简工作法则- 泰普勒人生
  20. 利盟Lexmark Intuition S508 一体机驱动

热门文章

  1. led伏安特性实验误差分析_电阻元件的伏安特性 电阻元件的伏安特性实验结果误差分析...
  2. UiLanguage 语言-国家代码表
  3. Newton-Raphson法求解非线性方程复根
  4. java电话号码生成器
  5. PS后期一键调出紫色梦幻红外线照片效果
  6. 【ffplay】视频的宽高比详解 -PAR、DAR 和 SAR
  7. 7-1 厘米换算英尺英寸
  8. 不能将下载行为传输到IDM(亲测有效)
  9. php楼梯有n级台阶,楼梯问题的一些解决方法
  10. u盘启动蓝屏 索尼vaio_索尼笔记本如何设置u盘启动_索尼笔记本bios设置u盘启动教程介绍...