Java使用byte数组实现bit array

Bitmap类介绍

最近在考试,一直复习的有点枯燥.于是想着在闲余时间练一下Java代码,就写了这么一个bit array的实现,并且利用这个bit array完成二进制,十进制以及十六进制值的相互转换.

我写的实现类最初起名为bitmap(与数据结构bitmap没有关系),后来就懒得修改了,其实两个名称在这里是同一个意思,就不用太纠结他们的称呼了.

Bit array简介

Bit array 顾名思义就是一个以bit为数据结构的数组.每个bit只能拥有两个值,0和1.他们可以简单的理解为false与true值(boolean),所以在不同的使用环境下,他们可以拥有不同的解释.比如控制某些开关的映射: on/off;控制某些值的是否有效: valid/invalid,等等..在这些环境中,最简单最有效的方式当然是使用bit作为数据结构,既节省空间,操作又方便.


Java实现

很可惜的是,java并没有提供bit这种数据类型,即使最小的数据类型byte,也要占到8个bit.(以前从哪里看到过boolean值在不同的jvm实现下面可能是1bit,也可能是8bit,不过我对这个说法表示怀疑...).

所以在这里我考虑使用一个byte数组来实现bit array,不过这样就导致了不能使用原本很简单的数组操作,而是需要使用稍微复杂一点的比特操作(bitwise operation)来实现:

 void set(int)      将某一位的bit值点亮(设置为1)

boolean get(int)      获得某一位bit的值(0或1)

void clear(int)   将某一位的bit值清空(设置为0)

String toString() 用位的结构展现这个bitmap

这里要提一下,因为是byte数组而不是bit数组,所以如果直接看数组的值可能会完全莫不找头脑.因为这里的表达方式是一个十进制值,而不是二进制值.

比如,一个长度为8个bit数组应该是这样的:

0 0 1 0 0 0 1 1

但是如果把这个值当作byte类型处理,这个值就变成了

67

再比如:

11111111 11111111 11111111 11111111   (十进制为-1)

如果放在我的byte数组中,数组的长度需要为4,而这个数组的值为:

[-1, -1, -1, -1]

这样是完全看不出来bitmap的内部结构的,所以这里提供了一个toString()方法,将这个值重新以二进制的方式表现出来.


除此之外,我还提供了大部分逻辑门的操作:

逻辑门可以接受n个输入(n>=1,根据不同的门决定不同的数量,比如not只能有一个输入,而or和and理论上可以有无数个),然后经过一定的逻辑运算,得出一个1或0的输出.逻辑门是集成电路上最基础的组件,所有的运算都离不开他们.

一般逻辑门的输入和结果都是以这样的表来表示:

下面是一个and的示例:

input1 input2 output
0 0 0
0 1 0
1 0 0
1 1 1

这里篇幅有限,就不把所有的表列出来了,只是提供一下他们的逻辑结果:

操作 结果
not(a) 将所有值反转.
or(a, b)  两个值中其中一个为1
and(a, b) 两个值都是1,结果为1
nor(a, b)  两个值都是0,结果为1
xor(a, b)  两个值不相同的,结果为1
xnor(a, b) 两个值相同,结果为1
nand(a, b) 两个值都不是1,结果为1

(这里值得提一下,所有的逻辑门,都可以使用纯nand门组合来完成)


以上功能我打算一半以bit为单位操作完成,另一半使用更快捷的byte为单位操做完成.

Java类库中有一个BitSet类(不是Set数据结构)也是实现了类似的功能,当然这个类能做的东西远远比我实现的要多,比如可以改变bitmap大小,可以接受各种方式传输的值,等等..我查看了一下这个类的源码.它使用了一个long数组作为基础,实现方式基本与我的bitmap类似.

最大的区别就是,BitSet类的操作都是建立在效率的基础上,而我的更侧重于展现一个bit数组的操作方式,所以很多地方并没有使用提供最大效率的代码.


位操作(Bitwise operator)

因为这个实现使用了byte数组,所以必须使用大量的位操作.

这里使用位操作不仅可以使得程序更清晰(是的,如果不用位操作,这样的程序绝对没有一个人可以看懂)而且会让程序效率增加(其实也增加不了多少...有些地方为了使代码更清晰,多出了很多多余的操作).

最重要的,是可以给大家展现一个位操作的应用场景.

程序中常用的位操作有:

1) | or  或

比较两个bit的值,当其中一个是1时,结果为1

例如

12 | 54 

8-bit二进制的表示:

12
0 0 0 0 1 1 0 0
0 0 1 1 0 1 1 0
54

的结果为

0 0 1 1 1 1 1 0

再将这个数换算成10进制也就是2^1+2^2+2^3+2^4+2^5 = 62

应用场景:

当需要点亮某一个值的时候,可以将1移位到那个地方,然后用原值与这个移位的1使用|操作,这样原值的其他所有位的值都得以保留,而且新的位置上面不管以前是0还是1,都会变成1.

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		
		bitmap[byteOffset] |= (byte) (1 << bitOffset);		
上面的代码就是set方法里面的,实现了将某一位设置为1的这个效果.

2) & and 与

比较两个bit的值,当两个bit都为1的时候,才将结果设置为1

例如

12 & 54

12
0 0 0 0 1 1 0 0
0 0 1 1 0 1 1 0
54

的结果为

0 0 0 0 0 1 0 0

再将这个数换算成10进制也就是2^2= 4

应用场景:

如何判断一个数的某一个bit是不是1呢?

设置a = 1,然后将这个1移位到相应的bit位上面,这个时候a除了当前的bit位是1,其他的位都是0,然后让a与原值进行&操作.结果就是,所有其他的bit位都被忽略,只有这个需要的bit位的值被提取出来,如果这一位是0,结果==0;如果这一位是1,那么结果 !=0 (因为考虑到负数的存在,所以这个值是有可能小于0的).

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		
		return (byte) (bitmap[byteOffset] & (1 << bitOffset)) != 0;

这里就是程序中get方法的代码,他将1移到了需要检查的位置上,然后进行&操作,检查了要求的那一位数是不是1.

3) ~ complement 取反

这是个一位运算符,将一个bit的值反转:0反转为1,1反转为0

例如:

12
0 0 0 0 1 1 0 0

的结果为

1 1 1 1 0 0 1 1

再将这个数换算成10进制也就是-2^7 + 2^6 + 2^5 + 2^4 + 2^1 + 2^0= -128+64+32+16+2+1 = 125

这个操作也有大用.

假设你需要清空某一位,但是需要保留其他所有位的值,就可以将1移到需要清空的位置上,反转.这个时候,这个原来是1的bit为就变成0,而其他所有的bit位都是1了.再将这个数与其他值进行 & 操作,就可以保证在清空这一位数的同时,其他所有bit位的值得以保留.

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		byte complement = (byte) ~(1 << bitOffset);
		
		bitmap[byteOffset] &= (byte) complement;

程序中的clear方法,就是用到了取反操作.

4) ^ xor 异或

使用^比较两个值,如果这两个值相同,结果就是0,也就是说这个操作符主要是看两个值是否相同的.

例如

12 ^ 54

12
0 0 0 0 1 1 0 0
0 0 1 1 0 1 1 0
54

的结果为

0 0 1 1 1 0 1 0

再将这个数换算成10进制也就是2^5 + 2^4 + 2^3 + 2^1 = 58

应用场景:

这里就不多做解释了,大家可以考虑一下: x ^ ~0 这个是什么效果呢:)?


5) << left shift 左移位

 x << n 左移位就是将x的全部bit位向左边移n位,如果这些位数有越界的,就忽略他们.

例如:

12 << 3

12
0 0 0 0 1 1 0 0

的结果为

0 1 1 0 0 0 0 0

再重新解析为十进制: 2^6 + 2^5 = 96

这个数是什么呢?就是12 * 2^3 = 96

也就是说,左移n位就是让这个数乘以2^n


如果12是一个byte值(8bit),那么如果让12移动4位,会发生什么?

12
0 0 0 0 1 1 0 0

的结果为

1 1 0 0 0 0 0 0
这个时候最高位符号位称为了1,这个数就变成了负数! -2^7 +2^6 = 64

如果再向左移动两位呢?

这个数就变成-128了( -2^7).

这里要注意,符号位的值在右移位是会被保留的,所以这里的值如果符号位已经是1,那么无论再向右移动多少,符号位始终都是1.

应用场景:

(1 << bitOffset)

6) >> right shift 右移位

与左移位相似,这里是将一个数的比特位向右移动.不多做介绍了.


Bitmap代码


package bitmap;

import java.util.Arrays;

public class Bitmap {

	public static final int TRUE = 1;
	public static final int FALSE = 0;
	
	/**
	 * Byte-shifted position represents index of the bitmap array
	 */
	protected final int BYTE_SHIFT = 3;

	/**
	 * Values are stored in a byte array
	 */
	protected final byte[] bitmap;

	/**
	 * Number of elements in bitmap
	 */
	protected final int size;
	
	public Bitmap() {
		this(8, FALSE);
	}
	
	public Bitmap(int size) {
		this(size, FALSE);
	}
	
	public Bitmap(byte[] bitmap) {
		this.bitmap = Arrays.copyOf(bitmap, bitmap.length);
		size = bitmap.length * 8; // length << BYTE_SHIFT
	}
	
	/**
	 * Create a bitmap
	 * @param size number of elements in bitmap
	 * @param value TRUE : set all available bits to 1
	 * 				 FALSE : all available bits remain 0
	 */
	public Bitmap(int size, int value) {
		int bytes = (int) Math.ceil((double) size / 8);//number of bytes
		
		bitmap = new byte[bytes];
		this.size = size;
		if (value == TRUE)
			for (int i = 0; i < size; i++)
				set(i);
	}
	
	/**
	 * Set the position on bitmap to 1 
	 * @param position position on bitmap
	 */
	public void set(int position) {
		if (position > size) return;
		
		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		
		bitmap[byteOffset] |= (byte) (1 << bitOffset);		
	}	
	
	public void clear(int position) {
		if (position > size) return;

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		byte complement = (byte) ~(1 << bitOffset);
		
		bitmap[byteOffset] &= (byte) complement;	
	}
	
	/**
	 * Get the value of the position on bitmap
	 * @param position position on bitmap
	 * @return true if the position is 1,false else
	 */
	public boolean get(int position) {
		if (position > size) return false;

		int bitOffset = position % 8;
		int byteOffset = getByteOffset(position);
		
		return (byte) (bitmap[byteOffset] & (1 << bitOffset)) != 0;
		//this value may return -128 if the highest bit is 1(signed)
	}
	
	protected int getByteOffset(int position) {
		return bitmap.length - 1 - (position >> BYTE_SHIFT);
	}
	
	public Bitmap not() {
		for (int i = 0; i < bitmap.length; i++)
			bitmap[i] = (byte) ~bitmap[i];
		return this;
	}
	
	public Bitmap and(Bitmap map) {
		if (map.getSize() != size) return null;
		Bitmap result = new Bitmap(map.getBitmap());;
		byte[] byteArray = result.getBitmap();
		
		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] &= bitmap[i];
			
		return result;
	}
	
	public Bitmap or(Bitmap map) {
		if (map.getSize() != size) return null;
		Bitmap result = new Bitmap(map.getBitmap());
		byte[] byteArray = result.getBitmap();
		
		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] |= bitmap[i];
			
		return result;
	}
	
	public Bitmap xor(Bitmap map) {
		if (map.getSize() != size) return null;
		Bitmap result = new Bitmap(map.getBitmap());;
		byte[] byteArray = result.getBitmap();
		
		for (int i = 0; i < byteArray.length; i++)
			byteArray[i] ^= bitmap[i];
			
		return result;
	}
	
	public Bitmap nor(Bitmap map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		Bitmap result = new Bitmap(size);

		for (int i = 0; i < size; i++)
			if (!get(i) && !map.get(i))
				result.set(i);
		
		return result;
	}
	
	public Bitmap xnor(Bitmap map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		Bitmap result = new Bitmap(size);

		for (int i = 0; i < size; i++)
			if (get(i) == map.get(i))
				result.set(i);
		
		return result;
	}
	
	public Bitmap nand(Bitmap map) {
		if (map.getSize() != size) return null;
		int size = map.getSize();
		Bitmap result = new Bitmap(size);

		for (int i = 0; i < size; i++) {
			if (!(get(i) && map.get(i))) 
				result.set(i);
		}
		
		return result;
	}
	
	/**
	 * NOT
	 * complement_a[i] := not a[i]
	 * @param a 
	 * @return
	 */
	public static Bitmap complement(Bitmap a) {
		int size = a.getSize();

		for (int i = 0; i < size; i++)
			if (a.get(i))
				a.clear(i);
			else
				a.set(i);
		
		return a;
	}
	
	/**
	 * OR
	 * union[i] := a[i] or b[i]
	 * @param a
	 * @param b
	 * @return
	 */
	public static Bitmap union(Bitmap a, Bitmap b) {
		if (a.getSize() != b.getSize())
			return null;

		Bitmap map = new Bitmap(a.getSize());
		int size = a.getSize();
		
		for (int i = 0; i < size; i++)
			if (a.get(i) || b.get(i))
				map.set(i);
		
		return map;
	}
	
	/**
	 * AND
	 * intersection[i] := a[i] and b[i]
	 * @param a
	 * @param b
	 * @return
	 */
	public static Bitmap intersection(Bitmap a, Bitmap b) {
		if (a.getSize() != b.getSize())
			return null;
		
		Bitmap map = new Bitmap(a.getSize());
		int size = a.getSize();
		
		for (int i = 0; i < size; i++)
			if (a.get(i) && b.get(i))
				map.set(i);
		
		return map;
	}
	
	/**
	 * XOR
	 * difference[i] := a[i] and (not b[i])
	 * @param a
	 * @param b
	 * @return
	 */
	public static Bitmap difference(Bitmap a, Bitmap b) {
		if (a.getSize() != b.getSize())
			return null;

		Bitmap map = new Bitmap(a.getSize());
		int size = a.getSize();
		
		for (int i = 0; i < size; i++)
			if (a.get(i) != b.get(i))
				map.set(i);
		
		return map;
	}
	
	/**
	 * Returns the binary representation of bitmap.
	 * @return binary represetation of bitmap as String
	 */
	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		
		for (int i = 0; i < size; i++)
			if (get(i)) result.append("1");
			else result.append("0");
		
		return result.toString();
	}
	
	public byte[] getBitmap() {
		return bitmap;
	}
	
	public int getSize() {
		return size;
	}
	
}


小结


实现了这个类之后,操作二进制代码就方便多了.接下来,就可以利用这个类实现二进制,十进制,十六进制值的相互转换了.
具体的实现过程和介绍,请看下一篇文章.
http://blog.csdn.net/zimu666/article/details/8295724

欢迎点评!

你可能感兴趣的:(Java)