1. 程式人生 > >Java使用byte陣列實現bit array

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;
	}
	
}

小結

實現了這個類之後,操作二進位制程式碼就方便多了.接下來,就可以利用這個類實現二進位制,十進位制,十六進位制值的相互轉換了. 具體的實現過程和介紹,請看下一篇文章. 歡迎點評!