作者 l 会点代码的大叔(CodeDaShu)

有一道流传广泛的面试题:

给你一台 4G 内存的机器,一组 20 亿个无序正整数,如何快速地判断一个正整数 N 是否在这组数字中?或者如何快速地对这组数据排重后排序?

让我们先算算 20 亿个整数会占用多大的内存空间,Java 的 int 类型占用 4 个字节,那么 20 亿 * 4 再换算成 G 大约是 7.5G,大于题目中 4G 内存的限制,无法一次性地放到内存中;

这时候有些伙伴会说:“把数据放到磁盘上,然后分批将数据读取到内存中就行查询”,但是这种方法会导致多次磁盘 IO,而且只能解决第一个查找的问题,排序就没有办法做到了。

01

BitMap 的概念

BitMap 能够很好地解决这个问题;它是用一个 Bit 位来标记某个元素对应的 Value, 而 Key 即是该元素,比如我们初始化一个类型为 bit、长度为 8 的数组,数组下标 0-7,数组中的内容 1 表示存在,0 表示不存在,那么:

00000001 下标为 0 的位置,对应值是1,那么表示 0;同理:

00000010 表示 1;

00000100 表示 2;

00001000 表示 3;

...

10000000 表示 7;

如果一组数据 {2,3,4,7} 放到同一个数组中的话,就是 10011100:

如果按照 int 数组存储,{2,3,4,7} 需要 4 * 4 * 8 个 bit 才能存储的数据,但是现在 BitMap 只需要 8 个 bit 就可以存储,很大地节省了存储空间,并且排重后的排序也变的非常简单了;如果用 byte 实现的话,只需要 1 个 byte 就可以(1 byte = 8 bits)。

如果增加了一个数字 10 呢,那么 1 个 byte 就不够了:

02

数据结构及初始化

我们可以得知,BitMap 的容量大小取决于最大的那个数值,比如要存储 {2,3,4,7,10}:

  • 如果用 bit 数组实现(假如有的话),那么需要 10 + 1 个长度;

  • 如果是用 byte 数组实现,那么需要 10/8 + 1 个长度;

  • 如果是用 int 数组实现,那么就需要 10/32 + 1 个长度(1 个 int 等于 4 个 bytes,等于 32 个 bits);

明白了这点之后,一个简单的 BitMap 数据结构也就可以确定了:

public class BitMap {//数据private byte[] bits; //最大值private int max_value;//容量private int capacity;/*** 初始化* @param capacity*/public BitMap(int max_value){this.max_value = max_value;//1bit存储8个数据,存储最大值为 max_value 的数组需要 max_value/8+1 个 byte,除以8就是右移3位this.capacity = (max_value >> 3 ) + 1;bits = new byte[capacity];}
}

03

添加数据

添加数据,需要快速地定位到这个元素要存到整个数组中的哪个位置,这里有两个概念:

索引号 index:数据保存在整个数组的哪个下标中;

位置号 position:数据在这个下标元素的哪个位置;

比如 10 保存在 index = 1,position = 2(从 0 开始) 这个位置中,经推算可得:

index = N / 8
position = N % 8

知道了 10 保存的位置之后,怎么把对应位置的数据更改成 1 呢?可以用“位或”运算。将 10 添加到 BitMap 中的完整步骤如下:

  • 计算 index = 10/8 = 1 ;

  • 计算 position = 10%8 = 2 ;

  • 将 byte[1] 的数据与 0000100 做“位或”运算,其中 0000100 是通过对 1 左移 2 得到。

完整的代码如下:

public void add(int num){//数据保存在整个数组的哪个下标中int index = num / 8;//数据在这个下标元素的哪个位置int position = num % 8;bits[index] |= 1<<position;
}

04

判断数字是否存在

知道了如何判断数字的索引号和位置号之后,判断数字是否存在也就容易了,直接使用“位与”运算,代码如下:

public boolean contains(int num){if(num > max_value){return false;}//数据保存在整个数组的哪个下标中int index = num / 8;//数据在这个下标元素的哪个位置int position = num % 8;return (bits[index] & 1<<position) != 0;
}

05

测试

让我们做一下测试吧:

public class BitMapTest {public static void main(String[] agrs){BitMap bm = new BitMap(100);bm.add(1);bm.add(12);bm.add(14);bm.add(51);bm.add(71);bm.add(100);System.out.println("12:" + (bm.contains(12)?"存在":"不存在"));System.out.println("13:" + (bm.contains(13)?"存在":"不存在"));System.out.println("51:" + (bm.contains(51)?"存在":"不存在"));System.out.println("66:" + (bm.contains(66)?"存在":"不存在"));System.out.println("100:" + (bm.contains(100)?"存在":"不存在"));}
}

运行结果:

12:存在
13:不存在
51:存在
66:不存在
100:存在

从结果可以看到,判断的都很准确,当然这只是一个最简单的BitMap实现,它还存在着很多问题,比如我们必须知道数据中最大的那个数字是多少,这个可以采用动态扩容的方式解决;

在 JDK 中,已经有对应实现的数据结构类 java.util.BitSet,我们可以不用强撸 BitMap,直接使用 BitSet 就好了,或者使用谷歌封装的 EWAHCompressedBitmap。

06

优缺点

优点:

  • 占用内存空间低,可以极大地节约空间;

  • 运算效率高,查找、去重都不需要遍历全部数据;

缺点:

  • 所有的数据不能重复,相当于直接就是排重过的;

  • 如果数据只有两个:1 和 10000000,使用 BitMap 得不偿失,只有当数据比较密集时才有优势。

本章节介绍了 BitMap 的概念和基本实现,后续会介绍 BitMap 在实际开发中的应用。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

20 亿个数字在 4G 内存中如何去重排序:快来试一试 BitMap相关推荐

  1. Java快速生成20亿数字_20 亿个数字在 4G 内存中如何去重排序:快来试一试 BitMap...

    有一道流传广泛的面试题: 给你一台 4G 内存的机器,一组 20 亿个无序正整数,如何快速地判断一个正整数 N 是否在这组数字中?或者如何快速地对这组数据排重后排序? 让我们先算算 20 亿个整数会占 ...

  2. 对10亿个数据去重java_20 亿个数字在 4G 内存中如何去重排序:快来试一试 BitMap...

    有一道流传广泛的面试题: 给你一台 4G 内存的机器,一组 20 亿个无序正整数,如何快速地判断一个正整数 N 是否在这组数字中?或者如何快速地对这组数据排重后排序? 让我们先算算 20 亿个整数会占 ...

  3. JavaScript 中的数字在计算机内存中占多少个Byte?

    先说数字类型 number "JavaScript数字类型采用IEEE754标准定义的64位浮点数表示数字"---摘自犀牛书 下图是mdn里的解释 注:number内存空间占比是不 ...

  4. Java快速生成20亿数字_关于内存:Java-打印10亿到20亿

    我想打印1000000至1999999999. 我以为这是一件容易的事,但是蚀告诉了我一些有关内存错误的信息. 我该怎么办? 我想要一个文本数据,其中所有数字都来自 将1000000000至19999 ...

  5. 【面试被虐】如何只用2GB内存从20亿,40亿,80亿个整数中找到出现次数最多的数?...

    这几天小秋去面试了,不过最近小秋学习了不少和位算法相关文章,例如 [面试现场]如何判断一个数是否在40亿个整数中? [算法技巧]位运算装逼指南 对于算法题还是有点信心的,,,,于是,发现了如下对话. ...

  6. 【面试被虐】如何只用2GB内存从20亿,40亿,80亿个整数中找到出现次数最多的数?

    这几天小秋去面试了,不过最近小秋学习了不少和位算法相关文章,例如 [面试现场]如何判断一个数是否在40亿个整数中? [算法技巧]位运算装逼指南 对于算法题还是有点信心的,,,,于是,发现了如下对话. ...

  7. 内存自source开始有20个数字、大写字母、小写字母的混合串(比如:1234567890abceFGHIJ)编程将其中的数字符号挑选出来存储到dst开始的内存中,并将结果显示到CRT上。

    内存自source开始有20个数字.大写字母.小写字母的混合串(比如:1234567890abceFGHIJ)编程将其中的数字符号挑选出来存储到dst开始的内存中,并将结果显示到CRT上. (扩展A: ...

  8. 只用2GB的内存找出20亿个整数中找到出现次数最多的数

    要求有一个包含20亿个32位整数的文件,从中找到出现次数最多的数. 首先先分析一下,32位int类型的数占4B,20亿个4B 约为 8GB,只用2GB肯定不够.所以我们肯定需要将这20亿个数哈希到不同 ...

  9. 一道腾讯面试题:如何快速判断某 URL 是否在 20 亿的网址 URL 集合中?布隆过滤器...

    何为布隆过滤器 还是以上面的例子为例: 判断逻辑: 多次哈希: Guava的BloomFilter 创建BloomFilter 最终还是调用: 使用: 算法特点 使用场景 假设遇到这样一个问题:一个网 ...

最新文章

  1. python编程大赛队名_【推荐】程序员团队名称和口号word版本 (12页)
  2. Binder Driver浅析:Binder线程池
  3. ios wkweb设置图片_iOS 之 WKWebView自适应高度获取网络图片
  4. 百度地图API开发概述
  5. python pygame鼠标点击_Python中pygame的mouse鼠标事件用法实例
  6. 最大子序和:单调队列维护一个上升序列
  7. Android Shader类简介之渲染图像示例
  8. C++学习笔记(七)——log4cpp
  9. 444 nginx_nginx发布静态资源
  10. 永洪报表工具_报表工具对比选型系列用例——过程计算
  11. Python:实现miller rabin米勒-拉宾素性检验算法(附完整源码)
  12. sqlserver with 语法
  13. 生成二维码的三种方式
  14. 论文研读-AI4VIS-可视化推荐-Table2Analysis/Table2Charts
  15. python有n元人民币、其中有10元的_Python笔记-古灵阁小精灵金加隆金币兑换人民币...
  16. 《A Survey on Transfer Learning》迁移学习研究综述 翻译
  17. swap未禁用导致的k8s NotReady
  18. 串口硬盘与并口硬盘的区别
  19. VCC VDD VSS
  20. python之禅中英版

热门文章

  1. html5 原生 弹窗,一起来看 HTML 5.2 中新的原生元素 dialog
  2. java无刷新上传图片_【java实现web文件无刷新上传】
  3. PTA数据结构与算法题目集(中文)7-45
  4. 1013 Battle Over Cities (25分)(用割点做)
  5. SZUACM集训字符串基础总结: 字符串最小表示 ,KMP, EXKMP, Manracher, Trie树,字符串的hash; 附带一写常见的运用技巧,邝斌大佬的板子和例题[持续更新]
  6. python3笔记_python3 笔记
  7. python相对路径库_如何最简单、通俗地理解Python的搜索路径、相对路径、绝对路径?...
  8. linux通过串口读取文件,Linux 串口读写(二)
  9. 通俗讲java反射机制ioc,结合反射说明SpringIOC的实现原理
  10. 黑盒测试之等价类测试