java递归实现稀疏位图

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_23318779/article/details/82953921

由编程珠玑第二版第一章习题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

猜你喜欢

转载自blog.csdn.net/qq_23318779/article/details/82953921