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. XML 解析的两种方法

    申请博客有一段时间了,一直没有写些什么,今天写一下被遗忘的 xml,因为 ios 现在一般都用 JSON,但毕竟还有一部分老一些的服务器还会有 xml xml 格式的解析方式有两种 1.SAX解析: ...

  2. windows下解决端口被占用的问题

    步骤一.Windows查看所有的端口 点击电脑左下角的开始,然后选择运行选项,接着我们在弹出的窗口中,输入[cmd]命令,进行命令提示符.然后我们在窗口中输入[netstat -ano]按下回车,即会 ...

  3. 2018.11.06 bzoj1093: [ZJOI2007]最大半连通子图(缩点+拓扑排序)

    传送门 先将原图缩点,缩掉之后的点权就是连通块大小. 然后用拓扑排序统计最长链数就行了. 自己yyyyyy了一下一个好一点的统计方法. 把所有缩了之后的点都连向一个虚点. 然后再跑拓扑,这样最后虚点的 ...

  4. maven 中央仓库地址 随笔记下了

    Maven 中央仓库地址: 1. http://www.sonatype.org/nexus/ 2. http://mvnrepository.com/ 3. http://repo1.maven.o ...

  5. vue 开发系列(一) vue 开发环境搭建

    概要 目前前端开发技术越来越像后台开发了,有一站式的解决方案. 1.JS包的依赖管理像MAVEN. 2.JS代码编译打包. 3.组件式的开发. vue 是一个前端的一站式的前端解决方案,从项目的初始化 ...

  6. VSCode 设置侧边栏字体大小;Visual Studio Code改变侧边栏大小

    1.代码改写,进入默认安装的如下路径 C:\Users\Administrator\AppData\Local\Programs\Microsoft VS Code\resources\app\out ...

  7. 第15章:MongoDB-聚合操作--聚合管道--$match

    ①$match 用于对文档集合进行筛选,里面可以使用所有常规的查询操作符. 通常会放置在管道最前面的位置,理由如下: 1:快速将不需要的文档过滤,减少后续操作的数据量 2:在投影和分组之前做筛选,查询 ...

  8. Linux批量远程命令和上传下载工具

    https://github.com/eyjian/mooon/releases/tag/mooon-tools mooon_ssh:批量远程命令工具,在多台机器上执行指定命令 mooon_uploa ...

  9. 一个支持邮件带附件群发的java类

    import java.io.UnsupportedEncodingException;import java.util.ArrayList;import java.util.Date;import ...

  10. Surface 2装机必备软件指南

    新买的Surface到货了还不知道有什么用,每天就用来划划点点?有点太浪费了吧!跟哥走,哥给你推荐几款Surface 2装机必备的软件~应用商店,走起~ 初次使用看过来:Win8宝典 如果你是一个像我 ...