• 位图BiMap


    在此记录我学的位图相关知识


    是什么


    是一种数据结构。也可以认为是一种二进制数组。
    一个整型int是32位,用他的二进制能表示0-31这些整数是否出现过。

    我们现在看int,要深入到微观去看,即看它的二进制,它有32个bit位

    比如现在很多App上每月都有签到活动,一个月签到几次就送大礼包。这个功能我们就可以使用这种思想来实现。

     int calender; 用这一个整型来统计某个月的签到情况。
     

    1. day 表示第几天
    2. 签到:即第day天签到了,则
    3. int calender =0;
    4. calender |= (1 << (day & 31))
    5. //day表示第几天
    6. public void qiandao(int day){
    7. int calender =0; // 00000000 00000000 00000000 00000000
    8. calender |= (1 << (day & 31));
    9. System.out.println(calender);
    10. }
    11. @Test
    12. public void t1(){
    13. qiandao(2);
    14. //结果是 4。
    15. // 对应二进制 00000000 00000000 00000000 00000100
    16. // 说明第二天签到了
    17. }

    一个整型数组int[ ] a = new int[32];

    每个元素都是32位 ,  32*32= 1024。所以,这个数组能表示0-1023这些整数是否出现过。

    相当于连起来了。

    同理

    一个long 是64位,能表示0-63数字是否出现过。

    一个long数组 long[ ] b = new long[32], 32 * 64 = 2048 ,所以,这个数组能表示0-2047这些整数是
    否出现过。

    每一格子存储64个整数是否存在,a[0]可以表示0-64,a[1]可以表示64-127。返过来

    126>>6 = 1(即126/64=1),即126落在a[1]里,又因为126 & 63=62(即126 % 64 = 62),所

    以,具体来说是 a[1]的第62位表示126是否存在。

    若存在,如何将该位设置为1?

    使用 | 运算,即按位或运算。

    1L 是long形的数字1,有64位,00000000000........000000001

    1L << (126 &63) 然后 | 上a[1]

    即 a[1] = a[1] | ( 1L << ( 125 & 63))

    简写为 a[ 1 ] |= 1L << ( 125 & 63)

    可以把数组每个元素看出一个桶。

    把握关键点:添加时:

    ①确定该数 在数组中的哪个元素中(哪个桶中)--- /操作,用 >> 代替

    ②确定用 该元素的哪一位表示 ---- % 操作,用 &代替

    为什么代替?因为位运算速度飞快

    那如何删除呢?也很简单。使用 & ,用0 去和那个bit位进行 & 操作。

    代码实现位图

    看代码应该比较好懂。就是对二进制、位运算的一种巧妙应用。

    1. public class BitMap{
    2. public long[] bits;
    3. public BitMap(int max){//给定最大的数
    4. //若max=1,则数组长度得是1.若max=0,数组长度是1.抠边界。
    5. // >> 6 等价于 /64,但位运算速度更快
    6. bits = new long[(max+64) >> 6];
    7. }
    8. //num & 63 即num%64,看是该元素的第几位。
    9. public void add(int num){
    10. //两步:找到数组中对应的元素,然后,将相应位 弄成1. 必须写1L,表示是64位的1。
    11. //bits[num >> 6] = bits[num >> 6] | (1L << (num & 63));
    12. //简写为
    13. //bits[num >> 6 ]是确定在哪个桶里。num & 63 是确定在该桶的哪个位置,让1移动过去对准
    14. //每一个桶64位
    15. bits[num >> 6] |= (1L << (num & 63));
    16. }
    17. //删除
    18. public void delete(int num){
    19. bits[num >> 6] &= ~(1L << (num & 63));
    20. }
    21. public boolean contains(int num){
    22. return (bits[num >> 6] & (1L << (num & 63))) != 0;
    23. }
    24. }

       带有测试的

    1. public class BitMap{
    2. public long[] bits;
    3. //给定一个预估的最大值,初始化数组,看看最多需要几个格子(几个坑位)
    4. // 如果实际业务中,超过了,那就重写造一个
    5. public BitMap(int max){//给定最大的数
    6. //若max=1,则数组长度得是1.若max=0,数组长度是1.抠边界。
    7. //看看最多需要几个格子。 >> 6 等价于 /64,但位运算速度更快
    8. bits = new long[(max+64) >> 6];
    9. }
    10. //num & 63 即num%64,看是该元素的第几位。
    11. public void add(int num){
    12. //两步:找到数组中对应的元素,然后,将相应位 弄成1. 必须写1L,表示是64位的1。
    13. //bits[num >> 6] = bits[num >> 6] | (1L << (num & 63));
    14. //简写为
    15. //bits[num >> 6 ]是确定在哪个桶里。num & 63 是确定在该桶的哪个位置,让1移动过去对准
    16. //把1怼上去
    17. //每一个桶64位
    18. bits[num >> 6] |= (1L << (num & 63));
    19. }
    20. //删除 // 这个好理解,就是先非一下,然后和他与,这样可以保证其他位置不变,只变标志位
    21. public void delete(int num){
    22. bits[num >> 6] &= ~(1L << (num & 63));
    23. }
    24. public boolean contains(int num){
    25. return (bits[num >> 6] & (1L << (num & 63))) != 0;//注意:是!=0, 它的结果不一定全是1
    26. }
    27. //1L << (num & 63) 相当于一个可以滑动的游标,一个辅助工具。
    28. public static void main(String[] args) {
    29. System.out.println("测试开始!");
    30. int max = 10000;
    31. BitMap bitMap = new BitMap(max);
    32. HashSet set = new HashSet<>();
    33. int testTime = 10000000;
    34. for (int i = 0; i < testTime; i++) {
    35. int num = (int) (Math.random() * (max + 1));
    36. double decide = Math.random();
    37. if (decide < 0.333) {
    38. bitMap.add(num);
    39. set.add(num);
    40. } else if (decide < 0.666) {
    41. bitMap.delete(num);
    42. set.remove(num);
    43. } else {
    44. if (bitMap.contains(num) != set.contains(num)) {
    45. System.out.println("Oops!");
    46. break;
    47. }
    48. }
    49. }
    50. for (int num = 0; num <= max; num++) {
    51. if (bitMap.contains(num) != set.contains(num)) {
    52. System.out.println("Oops!");
    53. }
    54. }
    55. System.out.println("测试结束!");
    56. }
    57. }

    什么用

    当最大值确定时,就可以用位图收集数字,表示是否存在。好处是极大省空间。

    搞牛逼一点,就是布隆过滤器,布隆过滤器就是使用 位图 + 多个哈希函数 实现的

    位运算常识

    a << 1 等价于 a*2

    a<<2 等价于 a*4

    a<<3 等价于 a*8

    a<<4 等价于 a*16

    a>>1 等价于 a/2

    a >> 2 等价于 a/4

    a>>3 等价于 a/8

    a>> 4 等价于 a/16

    a>>5 等价于 a/32

    a>>6 等价于 a/64

    a%64 等价于 a & 63,

    a % n 等价于 a & (n-1),前提是 n必须是 2的k次幂,即n必须是 1、2、4、8、16、32、64、128...这样的数才行。

    在JDK8的  HashMap源码里,二次哈希值对数组长度取模,得到索引。也用了这个操作,

    hash & (length -1)

    位运算的速度很快。

  • 相关阅读:
    Prometheus 基本概念
    十五、异常(7)
    开源网安受邀参加2023澳门万讯论坛,引领软件安全领域国产化替代浪潮
    【自学前端】我只学这些够吗?好难
    eclipse教程
    基础练习 圆的面积
    优先队列与堆排序的时间复杂度分析
    Arduino安装 esp32 by Espressif (2.0.11)
    TDH社区版上新宽表数据库Hyperbase,轻松实现海量数据的毫秒级精确检索
    android 离线语言合成(文字转语音)
  • 原文地址:https://blog.csdn.net/weixin_60664694/article/details/133562906