没什么参考价值,之前的考虑有问题,这个其实就是个类似B树的结构
且每个节点存n byte的数据,但是有32*n byte的孩子指针。。
2019.7.18
由编程珠玑第二版第一章习题9引发的思考
用一维byte数组实现位图,有如下缺陷:
1、当使用一维位图存储比较稀疏的数据时,会有内存浪费。
2、当数据范围极其巨大时,无法用一维数组实现(大于long[Integer.Max]即2^96)
基于以上理由,设计使用多维数组实现位图来进行优化,多维数组可以按需创建,在数据稀疏时减少内存的浪费。
本人仍使用byte来存储bit位,可以使用int、long等单位来存储以提高存储密度与计算效率。
整体设计:为树形结构,每个节点包括1个byte8个bit位,以及8个孩子节点,结构图如下所示:
每个节点的孩子节点,可以在用到的时候在创建,对于稀疏数据来说,这样可以节省很多内存。
数据存储方式:本人设计使用byte存储bit位,因此使用八进制表示数据会比较方便,比如十进制668转成八进制为01234,存储过程类似将十进制转成8进制的过程(递归)
[root]节点执行:668 > 8 ->根节点存不下,需要在孩子节点中存
668 % 8 = 4 ->该数值需要存在[root]的[4]号孩子中中
668 / 8 = 83 ->在[root][4]中存储83
[root][4]执行:83 > 8 ->[root][4]节点存不下,需要在孩子节点中存
83 % 8 = 3 ->83应该存储在[root][4]的[3]中
83 / 8 = 10 ->在[root][4][3]号中存储10
[root][4][3]执行:10 > 8 ->[root][4][3]存不下,需要在孩子节点中存
10 % 8 = 2 ->10应该存储在[4][3]的[2]中
10 / 8 = 1 ->在[4][3][2]中存储1
[root][4][3][2]执行:1 < 8 ->本节点存的下,直接存,将bit的1号位置位,递归完成
数值n的置位操作,可以在log8(n)次递归中完成,若使用int存储bit位,可在log32(n)次内完成
该设计仍有一定的空间浪费:
00-07存储在根节点中,010-077存储在二层节点中,即第一层存储8个bit,第二层存储56个bit,第三层存储7*8*8个bit,在除了根节点以外的节点中,bit位只用了7个。
由于我是用n每次取余的值来确定存储于哪个孩子节点,并将n/8的值递归传递,最终n/8的值小于8时,即可直接存储于当前节点,由于n/8取值在1-7之间,因此节点中的bit位只能用7个(根节点可以直接存0,因此可能存8个)。
直观点表示,如下:
[root]:00-07
[root][0]:010.020.030.040.050.060.070——没有000
[root][1]:011.021.031.041.051.061.071——没有001
[root][2]:012.022.032.042.052.062.072——没有002
...
[root][6]:016.026.036.046.056.066.076——没有006
[root][7]:017.027.037.047.057.067.077——没有007
public class ByteArrayStack implements ByteArrayIntr{
public static void main(String args[]){
System.out.println(Long.SIZE);
ByteArrayStack b = new ByteArrayStack();
b.setFlag(1);
long startTime = System.currentTimeMillis();
for(int i = 0 ; i <= 10000000 ; i++){
b.setFlag(i);
b.hasFlag(i);
b.clearFlag(i);
}
System.out.println(System.currentTimeMillis() - startTime);
}
//bitmap存储数据的值
private byte flagPosi;
//即flagPosi的bit长度,单独拉一个变量,方便后续编码
private static int flagLength;
//子节点数组
private ByteArrayStack[] childStack;
public ByteArrayStack(){
this.flagPosi = 0;
this.flagLength = Byte.SIZE;
this.childStack = new ByteArrayStack[flagLength];
}
/**
* 标记byte的index位
* @param ori 要标记的byte
* @param index 要标记的数字
* @return 标记后的值
*/
public void setFlag(int index){
if(index >= flagLength){
//递归
int childPosi = index % flagLength;
if(childStack[childPosi] == null){
childStack[childPosi] = new ByteArrayStack();
}
childStack[childPosi].setFlag(index / flagLength);
}else{
//直接set
this.flagPosi |= 1 << index;
}
}
/**
* 去除byte的index位的标记
* @param ori 要去除标记的byte
* @param index 要去除标记的位置
* @return 标记后的值
*/
public void clearFlag(int index){
if(index >= flagLength){
//递归
int childPosi = index % flagLength;
if(childStack[childPosi] == null){
return;
}else{
childStack[childPosi].clearFlag(index / flagLength);
}
}else{
//直接set
this.flagPosi &= -2 << index;
}
}
/**
* 检测byte的index位,是否被标记
* @param ori 要检测的byte
* @param index 要检测第几位,0-7
* @return true为被标记,false未标记
*/
public boolean hasFlag(int index){
if(index >= flagLength){
//递归
int childPosi = index % flagLength;
if(childStack[childPosi] == null){
return false;
}else{
return childStack[childPosi].hasFlag(index / flagLength);
}
}else{
return (this.flagPosi >> index & 0x01) == 1;
}
}
public int[] getAll(){
return null;
}
}
1、有空间浪费,且10、20、30、40、50、60、70存在于[root][0]孩子中11、21、31、41、51、61、71存在于[root][1]孩子中这种存储方式,感觉不是很人性化,应该可以再优化
2、未考虑负数
3、flagLength可以优化去掉,使用byte、int、long存储bit位时,在执行取余、除法运算时,可以直接用位运算以加快速度,此处增加了flagLength反而可能会破坏了编译器的这种优化,即使flagLength的值为8