BitArray是C System.Collections內置的集合,用於幫助進行位運算。 BitArray的使用示例 我們先來看其中一個構造函數,構造函數要求提供BitArray的長度,然後通過GetArrayLength計算這個長度的位向量需要提供多少個int數據進行存儲,之後根據計算後的結果, ...
BitArray是C# System.Collections內置的集合,用於幫助進行位運算。
BitArray的使用示例
// 創建兩個大小為 8 的點陣列
BitArray ba1 = new BitArray(8);
BitArray ba2 = new BitArray(8);
byte[] a = { 60 };
byte[] b = { 13 };
// 把值 60 和 13 存儲到點陣列中
ba1 = new BitArray(a);
ba2 = new BitArray(b);
// ba1 的內容
Console.WriteLine("Bit array ba1: 60");
for (int i = 0; i < ba1.Count; i++)
{
Console.Write("{0, -6} ", ba1[i]);
}
Console.WriteLine();
// ba2 的內容
Console.WriteLine("Bit array ba2: 13");
for (int i = 0; i < ba2.Count; i++)
{
Console.Write("{0, -6} ", ba2[i]);
}
Console.WriteLine();
BitArray ba3 = new BitArray(8);
ba3 = ba1.And(ba2);
// ba3 的內容
Console.WriteLine("Bit array ba3 after AND operation: 12");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
ba3 = ba1.Or(ba2);
// ba3 的內容
Console.WriteLine("Bit array ba3 after OR operation: 61");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
Console.ReadKey();
可以看到只要BitArray完成構造後就可以和其他BitArray進行位運算了
構造原理
接下來看看BitArray是怎麼構造實現的,BitArray看起來是一個bool數組,但它內部實際上是由int數組實現的
我們來看看它的構造函數,根據構造函數我們很快明白BitArray內部的構造機制
public BitArray(int length, bool defaultValue)
{
if (length < 0)
{
throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
m_array = new int[GetArrayLength(length, 32)];
m_length = length;
int fillValue = defaultValue ? unchecked(((int)0xffffffff)) : 0;
for (int i = 0; i < m_array.Length; i++)
{
m_array[i] = fillValue;
}
}
private static int GetArrayLength(int n, int div)
{
return n > 0 ? (((n - 1) / div) + 1) : 0;//-1是防止算術溢出,+ 1是因為必然有一個int數
}
我們先來看其中一個構造函數,構造函數要求提供BitArray的長度,然後通過GetArrayLength計算這個長度的位向量需要提供多少個int數據進行存儲,之後根據計算後的結果,分配m_array這個int[] 數組。之後根據defaultValue這個bool變數為int數組中的int值設置預設值0x00000000或0xffffffff
再看下一個構造函數
public BitArray(byte[] bytes) {
if (bytes == null) {
throw new ArgumentNullException("bytes");
}
if (bytes.Length > Int32.MaxValue / BitsPerByte) {
throw new ArgumentException(Environment.GetResourceString("Argument_ArrayTooLarge", BitsPerByte), "bytes");
}
m_array = new int[GetArrayLength(bytes.Length, 32/8)];
m_length = bytes.Length * 32/8;
int i = 0;
int j = 0;
while (bytes.Length - j >= 4)
{
m_array[i++] = (bytes[j] & 0xff) |
((bytes[j + 1] & 0xff) << 8) |
((bytes[j + 2] & 0xff) << 16) |
((bytes[j + 3] & 0xff) << 24);
j += 4;
}
switch (bytes.Length - j)
{
case 3:
m_array[i] = ((bytes[j + 2] & 0xff) << 16);
goto case 2;
case 2:
m_array[i] |= ((bytes[j + 1] & 0xff) << 8);
goto case 1;
case 1:
m_array[i] |= (bytes[j] & 0xff);
break;
}
}
這次的構造函數是用byte數組初始化,byte是8位的,可以輕易計算出需要32/8個byte數據才能填充一個int數,相應的如果想要將byte中的數據填充到int中需要有所修改,需要將byte數據&0xff獲取8位的二進位數據,再通過移位和或運算一步步將數據填充進int中
構造函數還有好幾個,不過本質是一樣的。通過構造函數提供的信息獲取需要的int數據數量用以存放位向量的數據,然後填充數據
數據獲取和設置
再看看BitArray內部的其他實現
BitArray的Get
public bool Get(int index) {
if (index < 0 || index >= Length) {
throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
return (m_array[index / 32] & (1 << (index % 32))) != 0;
}
代碼本質就是位運算,通過index/32找到存儲的int數據,再通過與上(1 << (index % 32))獲取這個位置的二進位數據,然後通過!=0操作返回bool數據
BitArray的Set
public void Set(int index, bool value) {
if (index < 0 || index >= Length) {
throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (value) {
m_array[index / 32] |= (1 << (index % 32));
} else {
m_array[index / 32] &= ~(1 << (index % 32));
}
}
設置查找數據索引的用的是同樣的方法,找到後通過或1設置1,與~1設置0
BitArray長度設置
public int Length {
get {
return m_length;
}
set {
if (value < 0) {
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
int newints = GetArrayLength(value, 32);
if (newints > m_array.Length || newints + 256 < m_array.Length) {
int[] newarray = new int[newints];
Array.Copy(m_array, newarray, newints > m_array.Length ? m_array.Length : newints);
m_array = newarray;
}
if (value > m_length) {
int last = GetArrayLength(m_length, 32) - 1;
int bits = m_length % 32;
if (bits > 0) {
m_array[last] &= (1 << bits) - 1;
}
Array.Clear(m_array, last + 1, newints - last - 1);
}
m_length = value;
}
}
BitArray長度不是恆定的,當他改變時內部數組會重新生成,之前數組的數據會根據根據新的數組的長度拷貝一部分,源碼中拷貝有兩部分,前一部分是當內部數組newarray長度有變動時的拷貝,後一部分是在長度m_length有變動時的拷貝。
不過我覺得如果同時滿足newints > m_array.Length和value > m_length,後一部分的拷貝有些多餘
位運算
public BitArray And(BitArray value) {
if (value==null)
throw new ArgumentNullException("value");
if (Length != value.Length)
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer"));
Contract.EndContractBlock();
int ints = GetArrayLength(m_length, BitsPerInt32);
for (int i = 0; i < ints; i++) {
m_array[i] &= value.m_array[i];
}
return this;
}
public BitArray Or(BitArray value) {
if (value==null)
throw new ArgumentNullException("value");
if (Length != value.Length)
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer"));
Contract.EndContractBlock();
int ints = GetArrayLength(m_length, BitsPerInt32);
for (int i = 0; i < ints; i++) {
m_array[i] |= value.m_array[i];
}
return this;
}
public BitArray Xor(BitArray value) {
if (value==null)
throw new ArgumentNullException("value");
if (Length != value.Length)
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayLengthsDiffer"));
Contract.EndContractBlock();
int ints = GetArrayLength(m_length, BitsPerInt32);
for (int i = 0; i < ints; i++) {
m_array[i] ^= value.m_array[i];
}
return this;
}
public BitArray Not() {
int ints = GetArrayLength(m_length, BitsPerInt32);
for (int i = 0; i < ints; i++) {
m_array[i] = ~m_array[i];
}
return this;
}
雖說這一部分是BitArray的主要功能,但這一部分代碼卻十分簡單
不過需要註意的是,關於And,Or,Xor運算,參與位運算的2個BitArray數據需要擁有相同的Length
迭代器
BitArray繼承了介面ICollection,所以支持迭代集合,BitArray專門實現了BitArrayEnumeratorSimple類來實現GetEnumerator(),每次調用GetEnumerator()都會生成一個BitArrayEnumeratorSimple實例
public IEnumerator GetEnumerator()
{
return new BitArrayEnumeratorSimple(this);
}
private class BitArrayEnumeratorSimple : IEnumerator, ICloneable
{
private BitArray bitarray;
private int index;
private int version;
private bool currentElement;
internal BitArrayEnumeratorSimple(BitArray bitarray) {
this.bitarray = bitarray;
this.index = -1;
version = bitarray._version;
}
public Object Clone() {
return MemberwiseClone();
}
public virtual bool MoveNext() {
if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
if (index < (bitarray.Count-1)) {
index++;
currentElement = bitarray.Get(index);
return true;
}
else
index = bitarray.Count;
return false;
}
public virtual Object Current {
get {
if (index == -1)
throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted));
if (index >= bitarray.Count)
throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded));
return currentElement;
}
}
public void Reset() {
if (version != bitarray._version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
index = -1;
}
}
上面的代碼是一個標準的迭代器介面實現,一開始index =-1;只有當第一次調用MoveNext()時,index =0後正式指向一個元素;Current這個屬性返回的是currentElement,在MoveNext()調用前,currentElement是它的預設值;每次調用MoveNext(),index自增1,currentElement也會被更新,當index超過集合長度時,index和currentElement不會因為MoveNext()的調用而更新,並且MoveNext會返回false,迭代器對集合的遍歷也正式結束。
為什麼foreach時集合不能改變?因為迭代器源於設計模式中的迭代器模式,它本質是訪問一個容器對象中各個元素,而又不需暴露該對象的內部細節,所以迭代器迭代時集合理論上不允許被改變的,那迭代器如何保證提醒程式員不應該在使用迭代器迭代時修改內部元素呢?仔細觀察MoveNext()代碼,發現關於version變數檢測的異常拋出,沒錯正是這個version保證了迭代器模式的原則。在上述代碼里我為了保證代碼的可讀性,刪除了關於version的代碼,實際上int類型version變數在調用構造函數時初始化為0,每次修改集合時都會自增1,這就保證迭代器可以及時檢測到version的變更,判斷原集合是否變更。