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 |
這個數是什麼呢?就是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 |
如果再向左移動兩位呢?
這個數就變成-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;
}
}