​参考:

  • 腾讯43亿QQ号码用完后怎么办?
  • 腾讯三面:40亿个QQ号码如何去重

一、背景:

首先,明确两点:

  • QQ号是 unsigned int 类型(4字节无符号整数,共32bit), 也就是说 QQ号的取值范围是:[0,232−1]\color{blue}{[0, 2³² - 1]}[0,232−1]。
  • QQ号是一个长度为10位的整数,大约是43亿,这也是QQ号码的理论最大值。
  • QQ号码的最小值是 10001, 为什么tx要做这种限制呢?仅仅是早期的一个设定而已。

二、43亿QQ号所占内存大小计算:

其次,43亿QQ号所占内存大小是多少呢?512M

  • 一个 unsigned int 类型数据可以标识 0 ~ 31 这32个整数的存在与否(4Byte,0 ~ 31 )
  • 两个 unsigned int 类型数据可以标识 0 ~ 64 这64个整数的存在与否(8Byte,0 ~ 64 )

又因为QQ号是 unsigned int 类型(无符号整数,4Byte,32位),也就是说 QQ号的取值范围是:[0,232−1]\color{blue}{[0, 2³² - 1]}[0,232−1]。
因此可对 2³² 次方范围内的QQ号所占大小进行推导:

  1. 1Byte所能标识数的范围为0~7(1字节,8 位)
  2. 4Byte所能标识数的范围为0~31(4字节,32 位)
  3. 那么多少Byte才能够标识 数的范围为 0 ~ 2³²-1(2³² 位)呢?

综上,用 2³² / 32 来表示:0 ~ 2³²-1(2³² 位) 能包含多少个 0 ~ 31(32位) 范围的块。并且每个 0 ~ 31(32位) 的块儿大小为4字节,因此 2³² / 32 × 4字节 的结果就是 0 ~ 2³²-1 范围所占字节的总大小,也就是 43亿 QQ号所占字节总大小。
2³²位 / 32位 × 4字节 / 1024 / 1024 = 512M\color{blue}{512M}512M(1M=1024KB,1KB=1024Byte)

由此可见,512M的内存大小,就可以用来标识所有QQ号的存在与否。

三、bitmap的具体实现:

  • 把所有 bitMap 位数组依次拼接后可表示 0 ~ 2³² 范围内43亿的数,所以足够存储40亿不重复的账号了
  • bitmap数组中的一个uint32类型元素,由4字节组成(每个字节占8位):{[8位][8位][8位][8位]} {[8位][8位][8位][8位]} {[8位][8位][8位][8位]} {[8位][8位][8位][8位]}
  • 如:[0 1 2 3 4 5 6 7] [8 9 10 11 12 13 14 15] [16 17 18 19 20 21 22 23] [24 25 26 27 28 29 30 31][...] [...] [...] [... 40亿]
  • 共需 40亿/8位 = 5亿字节,5亿字节/4字节 = 1.25亿 个uint32类型元素数组来串联拼接表示(uint32类型大小占4字节),所占内存大小为:4000000000/8/1024/1024 ≈ 477M\color{blue}{477M}477M
  • 还原qq号(数组下标从0开始):当前第block块数 * 每个uint32 block块的固定大小为32 + 当前block块内的偏移量余数 yushu

  • 每次新增QQ号时,都只需要在 bitMap[block] 的结果基础上继续进行异或 ^ 运算即可 ~
  • 比如原本"QQ号=9(二进制表示 1 0000 0000,位下标为 9)“是有值的,现在又新增了"qq号=10(二进制表示 10 0000 0000,位下标为 10)”
  • 那么9和10相异或后得: 1 0000 0000 ^ 10 0000 0000 = 11 0000 0000,表示第 9 位和第 10 位的QQ号都有值了
  • 0x1<<(yuShu-1):比如原本"QQ号=9"时,那就把 0x1 左移8位,即:0x1 << 8,得到 100000000,最后从右至左第9位为1
  • 关键代码:bitMap[block] = bitMap[block] ^ (0x1 << (yuShu - 1)) // 设置标记位:在之前bitMap已有结果的基础上,设置第block块上的第 “(0x1 << (yuShu - 1))” bit位为1
// 把所有位数组依次拼接后可表示 0~2³² 范围内的数,所以足够存储40亿不重复的账号了
// bitmap数组中的一个uint32类型元素,由4字节组成(每个字节占8位):{[8位][8位][8位][8位]} {[8位][8位][8位][8位]} {[8位][8位][8位][8位]} {[8位][8位][8位][8位]} ...
// 如:[0 1 2 3 4 5 6 7][8 9 10 11 12 13 14 15][16 17 18 19 20 21 22 23][24 25 26 27 28 29 30 31] ... [...][...][...][... 40亿]
// 共需 40亿/8bit = 5亿byte,5亿byte/4byte = 1.25亿个uint32类型元素数组来串联表示(uint32类型大小占4字节),所占内存大小为:4000000000/8/1024/1024≈477M
// 还原qq号(数组下标从0开始):当前第block块数 * 每个block块的固定大小32 + 当前block块内的偏移量余数yushu// 每次新增QQ号时,都只需要在bitMap[block]的结果上继续进行异或^运算即可~
// 比如原本"QQ号=9(二进制表示 1 0000 0000,位下标为9)"是有值的,现在又新增了"qq号=10(二进制表示 10 0000 0000,位下标为10)"
// 那么相异或后得: 1 0000 0000 ^ 10 0000 0000 = 11 0000 0000,表示第9位和第10位的QQ号都有值了
// 0x1<<(yuShu-1):比如原本"QQ号=9"时,那就把0x1左移8位,即:0x1 << 8,得到 100000000,从右至左第9位为1// 根据QQ号来设置其在bitMap中bit位
func BitCalculation(bitMap []uint32, everyBlockSize, qq uint32) {// 判断是属于第几块的第几个bit位block := qq / everyBlockSize // 块数:第block块(bitmap数组从第0块开始,无需根据yuShu是否为0来判断block是否+1)yuShu := qq % everyBlockSize // 余数:第block块中的具体第几个bit位// fmt.Printf("block=%v, yuShu=%v \n", block, yuShu)fmt.Printf("在对%v(%b)位运算设置标记位1之前,bitMap的第%v块上二进制表示: %b\n", qq, 0x1<<(yuShu-1), block, bitMap[block])bitMap[block] = bitMap[block] ^ (0x1 << (yuShu - 1)) // 设置标记位:在之前bitMap已有结果的基础上,设置第block块上的第 "(0x1 << (yuShu - 1))" bit位为1fmt.Printf("在对%v(%b)位运算设置标记位1之后,bitMap的第%v块上二进制表示: %b,还原qq号为: %v\n\n", qq, 0x1<<(yuShu-1), block, bitMap[block], block*everyBlockSize+yuShu)
}// 初始化相关参数
func TestBit(qqCnt uint32, qqArr []uint32) {var everyBlockSize uint32 = 4 * 8  // uint32类型占32位:一个uint32能标记0~31范围内32个QQ号blockCnt := qqCnt / everyBlockSize // 40亿QQ号所需uint32类型的块数bitMap := make([]uint32, blockCnt) // 存储40亿QQ号的bitmap,统一初始化为0// fmt.Println("blockCnt 40亿QQ号所需uint32类型的块数: ", blockCnt) // 1.25亿块for i := 0; i < len(qqArr); i++ {// 设置每个测试QQ到bitMap中BitCalculation(bitMap, everyBlockSize, qqArr[i])}
}func main() {// 以下均默认在64位操作系统下执行:// i := int(1)                   // int在64位系统下默认大小为8,即:int64// fmt.Println(unsafe.Sizeof(i)) // 8//// j := int32(1)// fmt.Println(unsafe.Sizeof(j)) // 4//// k := int64(1)// fmt.Println(unsafe.Sizeof(k)) // 8 相当于long/double类型,占8字节(64位) 最大能表示范围为:2⁶⁴//// ll := float64(1)// fmt.Println(unsafe.Sizeof(ll))// int32 表示范围: -2147483648 ~ 2147483647,因为账号数有40亿个,所以采用无符号类型 uint32 类型,表示范围: 0 ~ 4294967295var qqAccountCnt uint32 = 4000000000testNumArr := []uint32{9, 399999999, 1234567890} // test dataTestBit(qqAccountCnt, testNumArr)return
}

示例结果展示:

四、扩展

练习一:文件中有40亿个互不相同的QQ号码,请设计算法对QQ号码进行排序\color{blue}{排序}排序,内存限制1G。
很显然,直接用bitmap,
标记这40亿个QQ号码的存在性,然后从小到大遍历正整数,当bitmapFlag的值为1时,就输出该值,输出后的正整数序列就是排序后的结果。
请注意,这里必须限制40亿个QQ号码互不相同。通过bitmap记录,客观上就自动完成了排序功能。

练习二:文件中有40亿个互不相同的QQ号码,求这些QQ号码的中位数\color{blue}{中位数}中位数,内存限制1G。
一些刷题经验丰富的人,最开始想到的肯定是用堆或者文件切割,这明显是犯了本本主义错误。直接用bitmap排序,当场搞定中位数。

练习三:文件中有40亿个互不相同的QQ号码,求这些QQ号码的topK\color{blue}{topK}topK,内存限制1G。
很多人背诵过topK问题,信心满满,想到用小顶堆或者文件切割,这明显又是犯了本本主义错误。直接用bitmap排序,当场搞定topK问题。

练习四:文件中有80亿个QQ号码,试判断其中是否存在相同\color{blue}{相同}相同的QQ号码,内存限制1G。
一些吸取了经验教训的人肯定说,直接bitmap啊。然而,又一次错了。根据容斥原理可知:
因为QQ号码的个数是43亿左右(理论值2^32 - 1),所以80亿个QQ号码必然存在相同的QQ号码。

五、总结:

  • QQ号理论上的范围为:10001 - 43亿(2³²),其类型为 unsigned int
  • 43亿QQ号所占内存大小,经计算后大约占 512M (满足小于1G的要求)
  • 40亿QQ号所占内存大小,经计算后大约占 477M (满足小于1G的要求)
  • bitMap实现方案 及 异或 ^ 等位运算
  • 利用 bitMap 来处理海量数据问题,内存占用低,满足要求且可以取得不错的效果。包括:排序、中位数、topK、去重 等等…

利用bitmap处理海量数据问题:43亿QQ号所占内存大小为什么是512M?40亿个QQ号如何去重?相关推荐

  1. 京东物流收购德邦股份:崔维星将套现40亿 失去公司控股权

    雷递网 雷建平 3月12日报道 京东物流与德邦物流昨日达成战略合作,京东物流将收购德邦股份66.49%股份. 双方将在快递快运.跨境.仓储与供应链等领域展开深度合作,并将继续保持品牌和团队独立运营. ...

  2. 《哪吒》冲40亿!导演40岁改命背后:永远去做你余生中最重要的那件事

    作者 l 粥左罗 来源 l 粥左罗(ID:fangdushe520) 转载请联系授权(微信ID:shoujirym8754) 10年前,虚岁30的饺子在一篇自述里自嘲: "名牌医科大学毕业转 ...

  3. 腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?

    1.腾讯面试题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中? 思想:用数组来存这40亿个数,而且只能用bit来表示.why? ...

  4. 输血40亿,能“拯救”亏损的四维图新吗?

    日前,四维图新公告称,已完成新增非公开发行A股股票,收到投资者缴入认购3.2亿股新股的认购资金合计人民币40亿元. 本次募集资金将主要用于智能网联汽车芯片研发.自动驾驶地图更新及应用开发.自动驾驶专属 ...

  5. 腾讯太狠:40亿QQ号, 给你1G内存,怎么去重?

    说在前面 在40岁老架构师 尼恩的读者社区(50+)中,最近有小伙伴拿到了一线互联网企业如腾讯.美团.阿里.拼多多.极兔.有赞.希音的面试资格,遇到一几个很重要的面试题: 40亿Q号如何设计算法去重, ...

  6. 腾讯三面:40亿个QQ号码如何去重?

    我们来聊一道常见的考题,也出现在腾讯面试的三面环节,非常有意思.具体的题目如下: 文件中有40亿个QQ号码,请设计算法对QQ号码去重,相同的QQ号码仅保留一个,内存限制1G. 这个题目的意思应该很清楚 ...

  7. 腾讯三面:40 亿个 QQ 号码如何去重?

    今天,我们来聊一道常见的考题,也出现在腾讯面试的三面环节,非常有意思.具体的题目如下: 文件中有40亿个QQ号码,请设计算法对QQ号码去重,相同的QQ号码仅保留一个,内存限制1G. 这个题目的意思应该 ...

  8. 40亿个QQ号,限制1G内存,如何去重?

    40亿个unsigned int,如果直接用内存存储的话,需要: 4*4000000000 /1024/1024/1024 = 14.9G ,考虑到其中有一些重复的话,那1G的空间也基本上是不够用的. ...

  9. 采用位图存储40亿条qq号,用来记录登录状态

    假设有需要你在一个8G内存的容器里面做个服务,用来根据用户输入的qq号,判断此qq号是否登录过.qq总数有40亿,每天的登录记录了有1000亿条,每个qq号存储占4byte,设计下用哪种数据结构? 1 ...

最新文章

  1. 真牛X!这款通用数据库连接工具DBeaver!可以连接和操作市面所有的数据库!...
  2. 老板不爽,同事不满,下属不服,是你违反了这10大团队管理原则
  3. 【Python】学习笔记总结2(Python面向对象)
  4. JVM的内存配置参数
  5. 用java输入学生姓名查询成绩_编写一个Java程序,提示用户输入学生数量,学生姓名和他们的成绩,并按照成绩的降序打印学生姓名...
  6. 上传图片插件鼠标手cursor:pointer;不生效
  7. 【Flink】未解决 FLink 写 hive MemoryManager New Memory allocation smaller than the minimum allocation size
  8. java rsa enc 源码_RSA加解密源码 | 学步园
  9. P3371 单源最短路径【模板】 洛谷
  10. php 点击菜单栏只刷新局部,layui点击左侧导航栏,实现不刷新整个页面,只刷新局部的方法...
  11. 谈朋友圈——周围的朋友们
  12. weblogic数据源配置oracle and mysql
  13. 机器学习,斯坦福公开课
  14. 手把手教物体检测——M2Det
  15. 北复交浙科哈航邮中上科保研之路
  16. 阿里巴巴淘宝网电子商务模式调查分析
  17. STM32---DMA控制器(DMA)
  18. python程序实现分析_Python编程快速上手——疯狂填词程序实现方法分析
  19. swift android界面,使用 Swift 语言编写 Android 应用入门
  20. Power Apps平台利用CDS(Common Data Service)制作问卷调查

热门文章

  1. 云骑士装机大师可靠吗_是什么使云环境可靠?
  2. 赵小楼《天道》《遥远的救世主》深度解析(50)丁元英说的“女人是形式逻辑的典范,是辩证逻辑的障碍”“淡而又淡的名贵”是什么意思?
  3. Java基础—判断质数
  4. 朗强科技:HDMI信号分配传输器的功能与使用
  5. 培训班出来的程序员全是垃圾,没有例外?
  6. 【这个问题纠结了我好多年】3dMax到底使用Intel还是AMD的CPU更好?
  7. 35岁转行数据分析师可以吗?
  8. 芮成钢身上恰恰就缺少传统文化熏染
  9. ProteusMCU仿真——AT89C52
  10. 波士顿大学 计算机专业,波士顿大学计算机专业怎么样?7大课程任你选