时间记录:2019-8-11
我们知道在数据结构中有一种数据叫做位图的方式,在图像中就有一种叫做位图的东西。那么位图到底是一个怎样的数据结构呢。首先我们来了解下位图的数据结构的形式和其优势以及其不足之处在哪里。
位图的概念
我们在说long型数据占多少个字节,多少位。在java中long占8个字节每个字节占用8个bit,也就是占用8位,所以long占用64个bit,也就是64位。位图就是按照位来制定一个特殊的含义,代表了一个值,比如说long占64位,每一位代表一个数字,那一个long就可以占用0-63的数字了,那么在数据的存储上面是不是小很多。
注意: 在java的long和c中的long的区别,在native的编程中需要注意,这里不做阐述。
java中的bitset
java中对bitmap也有着应用,在bitset中就是使用着这种方式,将数据存储在long值中,主要的操作就是位操作,我们来先看下bitset的使用案例。
package com.huo.bitset;
import java.util.BitSet;
public class Test
{
public static void main(String[] args)
{
BitSet bitSet = new BitSet();
System.out.println(bitSet.get(10)+" "+bitSet.size());
bitSet.set(10);
System.out.println(bitSet.get(10)+" "+bitSet.size());
System.out.println(bitSet.get(65)+" "+bitSet.size());
bitSet.set(65);
System.out.println(bitSet.get(65)+" "+bitSet.size());
}
}
输出结果
false 64
true 64
false 64
true 128
我们发现在BitSet中的结果都是boolean类型的,那么里面存储的是boolean类型的?还有bitset的大小上面存在自动扩展的功能,那么这个为什么需要扩展呢,而且是2倍的扩展。在位上存在数则此为倍占用,表示这个数出现过。那么我们可以思考下这种类型的数据的应用场景。下面对bitset中对bitmap的应用分析。
bitset源码分析
我们知道bitset使用了bitmap的方式,那么具体是怎么实现的呢,我们来看源码。
我们来看下其构造函数
public BitSet() {
initWords(BITS_PER_WORD);
sizeIsSticky = false;
}
-------------------------------
public BitSet(int nbits) {
// nbits can't be negative; size 0 is OK
if (nbits < 0)
throw new NegativeArraySizeException("nbits < 0: " + nbits);
initWords(nbits);
sizeIsSticky = true;
}
-------------------------------
private BitSet(long[] words) {
this.words = words;
this.wordsInUse = words.length;
checkInvariants();
}
我们注意到了其中有initwords和sizeIsStick,wordsInUse等变量值得修改,那么这些值是干什么的呢。我们知道bitset是做存储的一种方式那么对应的是表示存储的参数值。
我们以第一个构造函数分析,然后来看其余的构造函数则可知道怎么回事
private void initWords(int nbits) {
words = new long[wordIndex(nbits-1) + 1];
}
|我们发现这里初始化了一个long的数组,对应的位图中的表示64位的一个数据,再来看这个大小
private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
|我们发现初始化了的数组大小表示为1,对应的是一个64位的数据,其中大小计算是均是移动6个位也就是64。
以上表示了初始化了一个数组,用来表示n*64的位数。
看完了初始化的内容,在来看get和set方法
get
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
checkInvariants();//做断言不看
int wordIndex = wordIndex(bitIndex);//计算在哪一段,也就是在0-63&& 64-127.....
return (wordIndex < wordsInUse)
&& ((words[wordIndex] & (1L << bitIndex)) != 0);//做&操作,如果相同则为true,否则为false,其实主要的就是64位中的哪一位
}
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
set
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);//计算属于那一段
expandTo(wordIndex);//如果大于数组的大小则进行扩大n+1,也就是超过了段
words[wordIndex] |= (1L << bitIndex); // Restores invariants 找到对应的位置设置值为1
checkInvariants();
}
private void expandTo(int wordIndex) {
int wordsRequired = wordIndex+1;
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired);
wordsInUse = wordsRequired;
}
}
private void ensureCapacity(int wordsRequired) {
if (words.length < wordsRequired) {
// Allocate larger of doubled size or required size
int request = Math.max(2 * words.length, wordsRequired);
words = Arrays.copyOf(words, request);
sizeIsSticky = false;
}
}
总结:总体上来说本身并没有什么太大的应用场景,但是这种思想确实很好的,在实际的设计中要多方应用。按照位的存储将实际的数字存储在其中,而实际只是一种表现的方式。平时要灵活变通不被固定套路搞死。
时间记录:2019-8-11