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的变更,判断原集合是否变更。

BitArray源码解析的更多相关文章

  1. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  2. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  4. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  5. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  6. Spring IoC源码解析——Bean的创建和初始化

    Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...

  7. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  8. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  9. jQuery2.x源码解析(回调篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...

随机推荐

  1. [转]MySQL索引原理及慢查询优化

    MySQL凭借着出色的性能.低廉的成本.丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库.虽然性能出色,但所谓“好马配好鞍”,如何能够更好的使用它,已经成为开发工程师的必修课,我们经常会从职位 ...

  2. 社区发现(Community Detection)算法(转)

    作者: peghoty 出处: http://blog.csdn.net/peghoty/article/details/9286905 社区发现(Community Detection)算法用来发现 ...

  3. (16)The beauty of what we'll never know

    https://www.ted.com/talks/pico_iyer_the_beauty_of_what_we_ll_never_know/transcript 00:13One hot Octo ...

  4. linux系统配置参数修改

    一.永久修改主机名修改/etc/sysconfig/network,在里面指定主机名称HOSTNAME=然后执行命令hostname 主机名这个时候可以注销一下系统,再重登录之后就行了. 或者修改/e ...

  5. 【服务器】Nginx文件配置

    nginx.conf文件 #运行用户 user nobody; #启动进程,通常设置成和cpu的数量相等 worker_processes 1; #全局错误日志及PID文件 #error_log lo ...

  6. myeclipse cannot connect to vm

    启动tomcat时,tomcat可以直接运行,而debug时弹出 解决方法:打开360安全卫士的功能大全找到修复网络(LSP)点击立即修复就可以使用debug

  7. 调试问题集之——Max10中配置完成后程序不能运行

    CONF_DONE信号是一个双向信号并且是Open-Drain.在配置过程中和配置之前作为输出,且为低电平.配置完成之后CONF_DONE作为输入脚,因为Open-Drain,所以必须由外部拉高,但二 ...

  8. create table b1 as select * from b建表锁表测试

    A: create table a1 like a; insert into a1 as select * from a; B: create table b1 as select * from b; ...

  9. 编译时:virtual memory exhausted: Cannot allocate memory,常见于VPS

    原文链接:http://blog.csdn.net/taiyang1987912/article/details/41695895 一.问题 当安装虚拟机时系统时没有设置swap大小或设置内存太小,编 ...

  10. DC画线

    CClientDC hdc(this);//获取DC CPen pen(PS_SOLID,4,RGB(255,0,0));//创建一支红笔 CPen * pOldPen=hdc.SelectObjec ...