主要谈谈BitSet的不足,而后重点说明开源Lucene的SparseFixedBitSet是如何解决的。数组
BitSet的优势在于节省存储空间,好比有10000个正整数范围从0-9999,底层使用byte数组方式存储大约占用1250个字节,若是使用整形数组存储大约占用4*10000=40000个字节,如此节省了大约32倍的空间。优化
若是有100个正整数,但数值范围在0-100000000之间,其中最大值为99999999,这个时候用BitSet存储将占用12500000个字节,而用整形数组存储大约占用100*4=400个字节,所以BitSet存储稀疏数据的时候反而是浪费空间的,解决的方法是使用稀疏BitSet,以Lucene的SparseFixedBitSet为例:索引
(1)首先将存储空间按照4096进行划分,即数值范围0-4095的为一组、4096-8191的为一组等等。it
SparseFixedBitSet中开辟了二维数组long[][]进行存储,数组的第一个下标表示组ID,好比1000的组ID=1000/4096=0。方法
(2)数组的第二个下标表示数值所在的数据块ID,好比在原始的bitset中(底层用long数组)1000的数据块ID=1000/64=15。这里的意思是同样的,但表示的方法不一样,也是最最关键的地方!很明显1000在数组的第二个下标中的位置必定不是15,否则费了这么大劲等于没优化,SparseFixedBitSet另外开劈了一个索引是用一维数组long[]表示的,数组下标是数值的组ID与二维数组的第一个下标同样的意思,long值记录的是1000的数据块ID=15(注意是按位进行存储的indexValue |=1<<15,因为1个long值能够表示64个数据块,每一个数据块的long能够存储64个数值正好64*64=4096,这就是为何存储空间按照4096划分的缘由。),因为只有1个数据块,因此数据块ID被变换成0,若是来了一个新数值800,它的数据块ID=800/64=12即indexValue |=1<<12,此时有两个数据块了,原来数据块ID=15的被变换成1,数据块ID=12的被变换成0,再来一个新数据600的时候indexValue |=1<<9,原来数据块ID=15的被变换成2,数据块ID=12的被变换成1,数据块ID=9的被变换成0。总结
(3)将上面的话用代码总结一下:数据
long[] index;二维数组
long[][] bits;底层
1000->index[0] |= 1<<15 ; bits[0][0] |=1<<1000;index
800 -> index[0] |=1<<12 ; bits[0][0] |=1<<800; bits[0][1] |=1<<1000;
600 -> index[0] |=1<<9 ; bits[0][0] |=1<<600; bits[0][1] |=1<<800; bits[0][2] |=1<<1000;
所以数据块ID=index中当前位后面1的个数:1000时index=00000000_00000000_00000000_00000000_00000000_00000000_01000000_00000000,因此1000的数据块ID=0;
800时index=00000000_00000000_00000000_00000000_00000000_00000000_01001000_00000000,因此1000的数据块ID=1,800的数据块ID=0;
600时index=00000000_00000000_00000000_00000000_00000000_00000000_01001001_00000000,因此1000的数据块ID=2,800的数据块ID=1;600的数据块ID=0;