BitMap和BitSet

一、BitMap算法简介

Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。由于采用了Bit为单位来存储数据,可以很大力度的节省空间,常用于对大量整数做去重和查询操作。

二、场景描述

在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存。

1byte=8bit
1kb=1024byte
1mb=1024kb
1gb=1024mb
java中 int类型占用4个字节=4*8=32bit

如果将20亿个整数放入内存中,需要占用多少内存?
  • 如果每个数字都以int类型存储
    20亿整数占用内存=20亿*4byte/1024/1024/1024 约等于 7.45G
  • 如果使用bit-map的思想,按位存储,即一位就可以代表一个数字
    20亿整数占用内存=20亿*1bit/8/1024/1024/1024=约等于0.233G

从上述结果来看,按位存储比按字节存储数字节约了(7.45/0.233-1约等于31倍空间),而且按字节存储根据内存4G的要求无法一次性在内存中进行处理。

三、BitMap算法原理

如何用一位来表示一个数呢?

众所周知,每一位的取值无非就是0和1,给每一位编号,那么用0表示数字存在,1表示数字不存在。
假如现在有一个字节也就是8位的空间,那么按照BitMap,可以表示8个数字,

QQ图片20200419113336.png

按照上图所示,该字节的8位表示了整数数组[6,5,2,0]。那么java中int类型占用了4个字节,也就是32位,那么就可以最多表示32个数字。超过32位数字呢?那就使用两个以上int去表示。

假设我们要存储的数字最大值位N,则申请int temp[1+N/32]的数组空间,其中:
temp[0]可表示 0~31
temp[1]可表示 32-63
.....以此类推
给定任一整数M,M数字所在数组中的下标位置就应该是M/32,M数字所在的位就是M%32

添加整数M

总共两步
1.找到整数所在数组temp的下标
int index = M/32
2.将temp[index] 的第M%32位置1

temp[index]=temp[index] | (1<<(M%32))

清除

根据bitMap的原理可知,想要清除掉一个数字,那就是将对应的位置0
总共两步
1.找到整数所在数组temp的下标
int index = M/32
2.将temp[index] 的第M%32位置0
temp[index]=temp[index] &(~ (1<<(M%32)))

查找

根据每一位代表一个数字,1表示存在,0表示不存在,那么只需要判断整数对应位是否位1即可
总共两步
1.找到整数所在数组temp的下标
int index = M/32
2.将temp[index] 的第M%32位置0
temp[index] &(1<<(M%32) != 0?"存在":"不存在"

四、BitMap的用处

  • 快速排序
    在添加元素的时候,本身就达到了有序状态,只需要遍历一遍输出即可,时间复杂度O(N)

  • 快速去重

  • 快速查找
    根据公式计算即可

五、手写BitMap

public class BitMap {
    private int[] temp;
    private int size;

    public BitMap(int size) {
        this.size = size;
        //初始化temp

        //由于一个int占32位,可表示32个数字,那么如果想要存储size个数字,则需要
        this.temp = new int[size >> 5 + 1];
    }

    /**
     * 存储num
     *
     * @param num 需要存储的整数
     */
    public void setBit(int num) {
        if (num < 0 || num > size) {
            throw new RuntimeException("");
        }
        int index = num >> 5;
        int bitIndex = num % 32;

        temp[index] |= (1 << bitIndex);
    }

    /**
     * 查找num
     *
     * @param num 需要存储的整数
     */
    public boolean getBit(int num) {
        if (num < 0 || num > size) {
            throw new RuntimeException("");
        }
        int index = num >> 5;
        int bitIndex = num % 32;

        return (temp[index] & (1 << bitIndex)) != 0;
    }

    /**
     * 删除num
     *
     * @param num
     */
    public void deleteBit(int num) {
        if (num < 0 || num > size) {
            throw new RuntimeException("");
        }
        int index = num >> 5;
        int bitIndex = num % 32;

        temp[index] &= (~(1 << bitIndex));
    }

    public static void main(String[] args) {
        BitMap bitMap = new BitMap(1000);
        bitMap.setBit(100);
        bitMap.setBit(400);
        System.out.println(bitMap.getBit(100));
        bitMap.deleteBit(100);
        System.out.println(bitMap.getBit(100));
    }
}

运行结果

true
false

六、BitSet

BitSet就是实现了Bit-Map算法。BitSet位于java.util包下,从JDK1.0开始就已经有了。该类实现了一个按需增长的位向量。位集的每一个组件都有一个boolean类型的值。BitSet的每一位代表着一个非负整数。可以检查、设置、清除单个位。一个BitSet可以通过逻辑与、逻辑或、逻辑异或去修改另一个BitSet。默认情况下,所有位的标识都是false。

6.1BitSet基本API

设值

// 设置一个整数,将其对应的位设置为true
set(int bitIndex)

// 设置一个整数,将其对应的位设置为value对应的值
set(int bitIndex, boolean value)

// 将[formIndex,toIndex)每一整数对应位设置为true
set(int fromIndex, int toIndex)

// 将[formIndex,toIndex)每一整数对应位设置为value对应的值
set(int fromIndex, int toIndex, boolean value)

清除

// 设置一个整数,将其对应的位设置为false
clear(int bitIndex)

// 将[formIndex,toIndex)每一整数对应位设置为false
clear(int fromIndex, int toIndex)

//将所有位都设置位false
clear() 

检查

// 返回该整数对应的位的值
boolean get(int bitIndex)

6.2 BitSet实现原理

BitSet有三种构造方法,我们直接来看无参构造器

private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;

    public BitSet() {
        initWords(BITS_PER_WORD);
        sizeIsSticky = false;
    }

    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数组存储。那么long类型占用8个字节,即64位,一个long类型可表示64个数字。默认设置BitSet可表示最大的位数为64位。与上述自己实现的基本类似。

再来看set方法

    public void set(int bitIndex) {
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

       //获取整数所在数组中位置
        int wordIndex = wordIndex(bitIndex);
        // 确保当前位集容量,如果已经超出当前容量,则扩容
        expandTo(wordIndex);
        // 将整数对应的位设置为1
        words[wordIndex] |= (1L << bitIndex); // Restores invariants

        checkInvariants();
    }

get方法

    public boolean get(int bitIndex) {
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        checkInvariants();

        int wordIndex = wordIndex(bitIndex);
        return (wordIndex < wordsInUse)
            && ((words[wordIndex] & (1L << bitIndex)) != 0);
    }

clear方法

    public void clear(int bitIndex) {
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        int wordIndex = wordIndex(bitIndex);
        if (wordIndex >= wordsInUse)
            return;

        words[wordIndex] &= ~(1L << bitIndex);

        recalculateWordsInUse();
        checkInvariants();
    }

可以看到JDK中的BitSet实现原理与第三节中一样,采用Bit-Map思想,BitSet封装较多的API,可供开发者们随意使用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容