位图

位图

1.位图的功能
2.位图的好处
3.位图的实现
 
例如有个集合,可以装数,3,5,-1,这个集合有add方法,有contains方法判断元素在不在,常见的有hashSet。但是这个有个问题是浪费空间的问题,比如你3进来,3这个数int类型,空间起码占4字节,4 byte,其实远远不止4字节,因为集合内部还有什么指针之类。一个数进来就4字节。
这个有点浪费了。
如果这个数是有范围的,能不能省点空间呢?
假设集合中只有一个int类型的数,int类型数,4字节,1字节= 8bit,共32位。如果集合只需要装0~31范围的数,那就可以这样:如果表示5,可以将第5位标记为1,如果表示0,可以将第0位标识为1。所以集合中这个整数是可以标识0~31之间的数是否出现过的。
 
如果这个集合用原来的hashSet哈希表的方式来存储需要的字节:32个数 * 4byte = 128字节。
现在只需要1个整数来表示0~31范围出现的数,只需要32bit,也就是4字节。空间节省了32倍。
 
但是问题是,0~31范围太小了,如果表示0~1023,怎么做?
0~1023是1024个数,前面知道一个int类型可以表示32个数,那么就需要:1024 / 32 = 32个int类型的数来表示。
采用int[]数组,长度为32。int[32]就可以表示1024个数。
int[0]:表示0~31
int[1]:表示32~63
...
int[31]: 表示xx ~ 1023
位图的功能:可以做出一个集合,如果数字范围是非常确定的,最大值很确定的话,我就可以用位图来实现收集数字并且告诉你它存不存在的功能。
位图的好处:极大的压缩空间。
 

位图的实现

long类型,一个long类型可以表示64个数。
 
代码实现:
package com.cy.class05;

import java.util.HashSet;

/**
 * 位图的实现
 */
public class Code02_BitMap2 {

    public static class BitMap {
        private long[] bits;

        /**
         * (max + 64) >> 6 -> (max + 64) / 64
         * 右移6位就是除64的意思
         */
        public BitMap(int max) {
            bits = new long[(max + 64) >> 6];
        }

        /**
         * num >> 6 -> num / 64,定位到bit数组对应下标的整数
         * num % 64 怎么写?
         * num % 64 -> num & 63
         *
         * 这里1L不能写成1,假设加170这个数,1左移42位,系统认为1是int类型,只有32位,左移就不对了。
         */
        public void add(int num) {
            bits[num >> 6] |= (1L << (num & 63));
        }

        public void delete(int num) {
            bits[num >> 6] &= ~(1L << (num & 63));
        }

        public boolean contains(int num) {
            return (bits[num >> 6] & (1L << (num & 63))) != 0;
        }
    }

    /**
     * 测试方法
     * bitMap:上面写的位图
     * set:哈希表
     * 以1/3的概率,两个共同加一个数
     * 以1/3的概率,两个共同删一个数
     * 再以1/3概率,你两共同查一个数
     * 最后吧所有从0~max的数查一遍,有任何一点对应不上打印Oops
     */
    public static void main(String[] args) {
        System.out.println("测试开始!");
        int max = 10000;
        BitMap bitMap = new BitMap(max);
        HashSet<Integer> set = new HashSet<>();
        int testTime = 10000000;
        for (int i = 0; i < testTime; i++) {
            int num = (int) (Math.random() * (max + 1));
            double decide = Math.random();
            if (decide < 0.333) {
                bitMap.add(num);
                set.add(num);
            } else if (decide < 0.666) {
                bitMap.delete(num);
                set.remove(num);
            } else {
                if (bitMap.contains(num) != set.contains(num)) {
                    System.out.println("Oops!");
                    break;
                }
            }
        }
        for (int num = 0; num <= max; num++) {
            if (bitMap.contains(num) != set.contains(num)) {
                System.out.println("Oops!");
            }
        }
        System.out.println("测试结束!");
    }
}
代码解释:
1.第12行,bits = new long[(max + 64) >> 6];  除以64 就是 >>6
 
2.add方法解释:
比如要将170这个数加入到位图中,位图的存储是个数组,那么加到哪个小标? ---- 170 / 64 这个下标位置。
170到底是这个bit[i]这个整数的第几位呢? ——170 % 64
为什么num % 64 = num & 63?
代码中当然你可以写num % 64,但是模运算是比较慢的。63是全1这个玩意,111111,第5位前面的数全是0了,比如:0000000000111111
将hum和63进行&运算之后,将第5位前面的数全部搞成0了。得到第5位之后的一个数。看下图deepseek的解释吧:
public static void main(String[] args) {
    int a = 0b00000000000000000000000001001100;     //76
    int b = 0b00000000000000000000000001000000;     //64
    int c = 0b00000000000000000000000000111111;     //63

    System.out.println(a % b);                      //12
    System.out.println(a & c);                      //12  即 1100
}
 
继续,现在知道要将这个数的第几位标记成1了,把170放进去,就是将这个位置的数标成1就行了:
将1左移n位得到的数,与原数进行或,就将这个1或进去了。
 
 
delete方法解释:
比如要将170删除,就是说要将bit数组中170位于的下标的数,在170那个位置上标0就行了:
 
 
contain方法解释:
要查找170这个数再这个集合中存不存在,就是判断数组中存170这个数组对应小标的那个数,且在170 % 64 = 42这个位置上的数,是不是等于0,如果等于0就不存在,如果等于1就存在。
 
 
 
 
 
 
 
 
 
 
 
 
--
posted on 2025-05-03 16:29  有点懒惰的大青年  阅读(21)  评论(0)    收藏  举报