chap06-BitSet

第一部分: 引用自 https://yq.aliyun.com/articles/136154
《java collection》学习

简介

JAVA中BitSet就是“位图”数据结构,根据“位图”的语义,数据的存在性可以使用bit位上的1或0来表示;一个bit具有2个值:0和1,正好可以用来表示false和true。

对于判断“数据是否存在”的场景,我们通常使用HashMap来存储,不过hashmap这个数据结构KEY和Value的保存需要消耗较多的内存,不适合保存较多的数据,即大数据场景;
比如在有10亿条URL中判定一个www.baidu.com/a是否存在?

  • a :如果我们使用常规的hashmap来保存将是不现实的,因为URL本身需要占据较多的内存而无法直接操作。
  • b : 如果我们使用bitset来保存,那么可以对一条URL求hashcode,并将数字映射在bitset上,那么事实上它只需要bitset上的一个bit位即可,即我们1位空间即可表达一个URL字符串的存在性。

BitSet原理
JAVA中,一个long型数字占用64位空间,根据上述“位图”的概念,那么一个long型数字就可以保存64个数字的“存在性”状态(无碰撞冲突时)。

比如50个数字{0,1,10,…63},判定“15”是否存在,

  • 如果是用数组或者hashmap保存,然后再去判定,那么保存这些这些数据需要占用64 * 64位;
  • 如果使用位图,那么一个long型数字即可。(如果换成50个字符串,那么其节约的空间可能更大)。
  1. BitSet只面向数字比较,如果像使用BitSet,那么可以将其hashcode值映射在bitset中。
  2. 首先我们需要知道:1,1<<64,1<<128,1<<192...等,这些数字的计算结果是相等的(位运算);
    这也是一个long数字,只能表达连续的(或者无冲突的)64个数字的状态,即如果把数字1在long中用位表示,那么数字64将无法通过同一个long数字中位表示–冲突;
    BitSet内部,是一个long[]数组数组的大小由bitSet接收的最大数字决定.这个数组将数字分段表示[0,63],[64,127],[128,191]…。
|------------|----------|----------|----------|----------|  
|  
|  数字范围      [0,63]     [64,127]  [128,191]     ...   |  
|------------|----------|----------|----------|----------|  
|  
|long数组索引      0           1          2         ...   |  
|------------|----------|----------|----------|----------|  
  1. bitSet内部的long[]数组是基于向量的,即随着接收的最大数字而动态扩展。

BitSet与Hashcode冲突
因为BitSet API只能接收int型的数字,即只能判定int数字是否在bitSet中存在。
所以,对于String类型,我们通常使用它的hashcode,但这有一种隐患,java中hashcode存在冲突问题,即不同的String可能得到的hashcode是一样的(即使不重写hashcode方法).
如果我们不能很好的解决这个问题,那么就会出现“数据抖动”—不同的hashcode算法、运行环境、bitSet容量,会导致判断的结果有所不同。

比如A、B两个字符串,它们的hashcode一样,
如果A在BitSet中“着色”(值为true),那么检测B是否在BitSet存在时,也会得到true。

这个问题该如何解决或者缓解呢?
我们可以对一个String使用多个hashcode算法生成多个hashcode
然后在同一个BitSet进行多次“着色”,在判断存在性时,只有所有的着色位为true时,才判定成功。

其实我们能够看出,这种方式降低了误判的概率。但是如果BitSet中存储了较多的数字,那么互相覆盖着色,最终数据冲突的可能性会逐渐增加,最终仍然有一定概率的判断失误。

所以在hashcode算法的个数与实际String的个数之间有一个权衡,我们建议: “hashcode算法个数 * String字符串的个数” < Integer.MAX_VALUE * 0.8

BloomFilter(布隆姆过滤器)
BloomFilter 的设计思想和BitSet有较大的相似性,目的也一致,它的核心思想也是使用多个Hash算法在一个“位图”结构上着色,最终提高“存在性”判断的效率。

bloomfilter

结论
BitSet同BloomFilter一样:

  • 如果判定结果为false,那么数据一定是不存在
  • 但是如果结果为true,可能数据存在,也可能不存在(冲突覆盖)


BitSet源码分析

在这里插入图片描述

注意:BitSet 并没有实现Set接口

构造函数

public class BitSet implements Cloneable, java.io.Serializable {
   
    //BitSets被打包成“words”数组
    //每个“word”表示一个long型数字,java中一个long占64bit, 2^6
    private final static int ADDRESS_BITS_PER_WORD = 6;
    private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; // 64
    private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1; // 63
    private static final long WORD_MASK = 0xffffffffffffffffL;

    private long[] words;
    private transient int wordsInUse = 0;

    //"words"大小是否由用户指定
    //如果是用户指定的,这说明用户知道他即将要做什么,程序会努力保护BitSet的大小不变;
    private transient boolean sizeIsSticky = false;


    //无参的构造函数
    public BitSet() {
        initWords(BITS_PER_WORD);
        sizeIsSticky = false; //false
    }


    public BitSet(int nbits) {
        // nbits可以为0,但是不能为负数
        if (nbits < 0)
            throw new NegativeArraySizeException("nbits < 0: " + nbits);

        initWords(nbits);
        sizeIsSticky = true;
    }

    //初始化数组长度
    private void initWords(int nbits) {
        words = new long[wordIndex(nbits-1) + 1];
    }

    //计算数组的长度,由于一个long占用64位
    //最终数组长度为 bitIndex / 64 == bitIndex >> 6
    private static int wordIndex(int bitIndex) {
        return bitIndex >> ADDRESS_BITS_PER_WORD;
    }
}   

set(),clear()
将第bitIndex数设置为true.

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

    //计算word所在数组的index:
    int wordIndex = wordIndex(bitIndex);
    expandTo(wordIndex);

    // 1L == 1L << 64 == 1L << 128 ...
    words[wordIndex] |= (1L << bitIndex); 

    checkInvariants();
}

private void expandTo(int wordIndex) {
    int wordsRequired = wordIndex+1;
    if (wordsInUse < wordsRequired) {
        //数组扩容
        ensureCapacity(wordsRequired);
        wordsInUse = wordsRequired;
    }
}

public void set(int bitIndex, boolean value) {
    if (value)
        set(bitIndex);
    else
        clear(bitIndex);
}

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

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

	//~(1L << bitIndex) 取反码
    words[wordIndex] &= ~(1L << bitIndex);

    recalculateWordsInUse();
    checkInvariants();
}

//按照区间设置
public void set(int fromIndex, int toIndex);
public void set(int fromIndex, int toIndex, boolean value);

get()
返回boolean,表示指定值是否被“着色”

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

     checkInvariants();

     //定位到数组index
     int wordIndex = wordIndex(bitIndex);
     return (wordIndex < wordsInUse)
         && ((words[wordIndex] & (1L << bitIndex)) != 0);
 }

get,set例

###step1,set(65)

set(65),wordIndex == 1, 1L << 65 == 2L;
words[1] = 0000000000000000000000000000000000000000000000000000000000000010 (2L)


###step2,get(65)

get(65), wordIndex == 1, 1L << 65 == 2L;
0000000000000000000000000000000000000000000000000000000000000010(2L)
&
0000000000000000000000000000000000000000000000000000000000000010(2L)
--------------------------------------------------------------------
2 != 0 == true


###step3,get(66)
get(66), wordIndex == 1, 1L << 66 == 4L;
0000000000000000000000000000000000000000000000000000000000000010(2L)
&
0000000000000000000000000000000000000000000000000000000000000100(4L)
--------------------------------------------------------------------
0 !=0 == false

逻辑运算

public void and(BitSet set) {
    this[index] &= set[index];
}
public void or(BitSet set){
    this[index] |= set[index];
}

//异或
public void xor(BitSet set){
    this[index] ^= set[index]
}

//先给set取非,然后&
public void andNot(BitSet set){
    this[index] &= ~set[index]
}

逻辑运算是计算是以数组内“word”为单位计算的,
如this[0] &= set[0],是比较【0,63】这个区间内所有的数字作and()操作。

假设原始set{1, 2, 3, 4, 5}和set{1, 3, 5, 7}.
在这里插入图片描述

size(),length()

public int size() {
	//数组长度*64
    return words.length * BITS_PER_WORD;
}

//返回最大的非0值所在位置;
public int length() {
    if (wordsInUse == 0)
        return 0;
 // 64 * wordsInUse - 最大数字的高位0的个数
 return BITS_PER_WORD * (wordsInUse - 1) +
    (BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1]));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值