如果需要处理的数字有许多位,C# 7为此提供了二进制字面量和数字分隔符。处理二进制数据时,还可以使用BitArray类和BitVector32结构。BitArry类位于名称空间System.Collections中,BitVector32结构位于名称空间System.Collections.Specialized中。这两种类型最重要的区别是,BitArray类可以重新设置大小,如果事先不知道需要的位数,就可以使用BitArray类,它可以包含非常多的位。BitVector32结构是基于栈的,因此比较快。BitVector32结构仅包含32位,它们存储在一个整数中。
BitArray类
BitArray类是一个引用类型,它包含一个int数组,其中每32位使用一个新整数。这个类的成员如下表所示。
注:按位运算符,它可以用于数字类型(如byte、short、int和long)。BitArray类具有类似的功能,但是可以用于不同数量的位,而不是用于C#类型。
BitArraySample使用如下名称空间:
System
System.Collections
System.Text
扩展方法GetBitFormat()遍历BitArray,根据位的设置情况,在控制台上显示1或0。为了获得更好的可读性,每4位添加一个分隔符:
using System;
using System.Collections;
using System.Text;
namespace 处理位的集合
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
public static class BitArrayExtensions
{
public static string GetBitsFormat(this BitArray bits)
{
var str = new StringBuilder();
for (int i = bits.Length - 1; i >= 0; i--)
{
str.Append(bits[i] ? 1 : 0);
if (i != 0 && i % 4 == 0)
{
str.Append("_");
}
}
return str.ToString();
}
}
}
说明BitArray类的示例创建了一个包含9位的数组,其索引是0~8。SetAll()方法把这9位都设置位true。接着Set()方法把对应于1的位设置为false。除了Set()方法之外,还可以使用索引器,例如,下面的第5个和第7个索引:
static void Main(string[] args)
{
var bits1 = new BitArray(9);
bits1.SetAll(true);
bits1.Set(1,false);
bits1[5] = false;
bits1[7] = false;
System.Console.WriteLine("initialized");
System.Console.WriteLine(bits1.GetBitsFormat());
Console.WriteLine("Hello World!");
}
这是初始化位的显示结果:
initialized
1_0101_1101
Not()方法会对BitArray类的位取反:
System.Console.WriteLine("not");
System.Console.Write(bits1.GetBitsFormat());
bits1.Not();
System.Console.Write(" = ");
System.Console.Write(bits1.GetBitsFormat());
Not()方法的结果是对所有的位取反。如果某位是true,则执行Not()方法的结果就是false,反之亦然。
not
1_0101_1101 = 0_1010_0010
这里创建了一个新的BitArray类。在构造函数中,因为使用变量bits1初始化数组,所以新数组与旧数组有相同的值。接着把第0、1和4位的值设置为不同的值。在使用Or()方法之前,显示位数组bits1和bits2。Or()方法将改变bits1的值:
var bits2 = new BitArray(bits1);
bits2[0] = false;
bits2[1] = true;
bits2[4] = false;
System.Console.Write($"{bits1.GetBitsFormat()} OR {bits2.GetBitsFormat()}");
System.Console.Write(" = ");
bits1.Or(bits2);
System.Console.WriteLine(bits1.GetBitsFormat());
使用Or()方法时,从两个输入数组中提取设置位。结果是,如果某位在第一个或第二个数组中设置为true,该位在执行Or()方法后就是true:
1_0101_1101 OR 1_0100_1110 = 1_0101_1111
下面使用And()方法作用于位数组bits1和bits2:
Console.Write($"{bits2.GetBitsFormat()} AND {bits1.GetBitsFormat()}");
Console.Write(" = ");
bits2.And(bits1);
System.Console.WriteLine(bits2.GetBitsFormat());
And()方法只把在两个输入数组中都设置为true的位设置为true:
1_0100_1110 AND 1_0101_1101 = 1_0100_1100
最后使用Xor()方法进行异或操作:
System.Console.Write($"{bits1.GetBitsFormat()} XOR {bits2.GetBitsFormat()}");
bits1.Xor(bits2);
Console.Write(" = ");
System.Console.WriteLine(bits1.GetBitsFormat());
使用Xor()方法,只有一个(不能是两个)输入数组的位设置为1,结果位才是1.
1_0101_1101 XOR 1_0100_1110 = 0_0001_0011
BitVector32 结构
如果事先知道需要的位数,就可以使用BitVector32结构替代BitArray类。BitVector32结构效率较高,因为它是一个值类型,在整数栈上存储位。一个整数可以存储32位。如果需要更多的位,就可以使用多个BitVector32值或BitArray类。BitArray类可以根据需要增大,但BitVector32结构不能。
下面列出了BitVector32结构中与BitArray类完全不同的成员。
BitVectorSample使用如下名称空间:
System
Syatem.Collections.Specialized
System.Linq
示例代码用默认构造函数创建了一个BitVector32结构,其中所有的32位都初始化为false。接着创建掩码,以访问位矢量中的位。对CreateMask()方法的第一个调用创建了用来访问第一位的第一个掩码。调用CreateMask()方法后,bit1被设置为1。再次调用CreateMask()方法,把第一个掩码作为参数传递给CreateMask()方法,返回用来访问第二位(它是2)的一个掩码。接着,将bit3设置为4,以访问位编号3。bit4的值是8,以访问位编号4。
然后,使用掩码和索引器访问位矢量中的位,并且相应地设置字段:
var bits1 = new BitVector32();
var bit1 = BitVector32.CreateMask();//bit1 = 1;
var bit2 = BitVector32.CreateMask(bit1);//bit2 = 2;
var bit3 = BitVector32.CreateMask(bit2);//bit3 = 4;
var bit4 = BitVector32.CreateMask(bit3);//bit4 = 8;
var bit5 = BitVector32.CreateMask(bit4);
bits1[bit1] = true;
bits1[bit2] = false;
bits1[bit3] = true;
bits1[bit4] = true;
bits1[bit5] = true;
System.Console.WriteLine(bits1);
BitVector32结构有一个重写的ToString()方法,它不仅显示类名,还显示1或0,来说明位是否设置了,如下所示:
BitVector32{00000000000000000000000000011101}
除了用CreateMask()方法创建掩码之外,还可以自己定义掩码,也可以一次设置多位。十六进制值abcdef与二进制1010 1011 1100 1101 1110 1111相同。用这个值定义的所有位都设置了:
bits1[0xabcdef] = true;
System.Console.WriteLine(bits1);
在输出中可以验证设置的位:
BitVector32{00000000101010111100110111101111}
把32位分别放在不同的片段中非常有用。例如,IPv4地址定义为一个4字节的数,该数存储在一个整数中。可以定义4个片段,把这个整数拆分开。在多播IP消息中,使用了几个32位的值。其中一个32位的值放在这些片段中:16位表示源号,8位表示查询器的查询内部码,3位表示查询器的健壮变量,1位表示抑制标志,还有4个保留位。也可以定义自己的位含义,以节省内存。
下面的例子模拟接受到值0x79abcdef,把这个值传送给BitVector32结构的构造函数,从而相应地设置为:
BitVector32 bits2 = new BitVector32(received);
System.Console.WriteLine(bits2);
在控制台上显示了初始化的位:
BitVector32{00001001101010111100110111101111}
接着创建6个片段。第一个片段需要11位,由十六进制值0xfff定义(设置了11位)。片段B需要8位,片段C需要4位,片段D和E需要3位,片段F需要两位。第一次调用CreateSection()方法只是接受0xfff,为最前面的11位分配内存。第二次调用CreateSection()方法时,将第一个片段作为参数传递。从而使下一个片段从第一个片段的结尾处开始。CreateSection()方法返回一个BitVector32.Section类型的值,该类型包含了该片段的偏移量和掩码。
//sections: FF EEE DDD CCCC BBBBBBBB AAAAAAAAAAA
var sectionA = BitVector32.CreateSection(0xfff);
var sectionB = BitVector32.CreateSection(0xff,sectionA);
var sectionC = BitVector32.CreateSection(0xf,sectionB);
var sectionD = BitVector32.CreateSection(0x7,sectionC);
var sectionE = BitVector32.CreateSection(0x7,sectionD);
var sectionF = BitVector32.CreateSection(0x3,sectionE);
把一个BitVector32.Section类型的值传递给BitVector32结构的索引器,会返回一个int,它映射到为矢量的片段上。这里使用一个帮助方法IntToBinaryString(),获得该int数的字符串表示:
System.Console.WriteLine($"Section A: {bits2[sectionA].ToBinaryString()}");
System.Console.WriteLine($"Section B: {bits2[sectionB].ToBinaryString()}");
System.Console.WriteLine($"Section C: {bits2[sectionC].ToBinaryString()}");
System.Console.WriteLine($"Section D: {bits2[sectionD].ToBinaryString()}");
System.Console.WriteLine($"Section E: {bits2[sectionE].ToBinaryString()}");
System.Console.WriteLine($"Section F: {bits2[sectionF].ToBinaryString()}");
IntToBinaryString()方法接收整数中的位,并返回一个包含0和1的字符串表示。在实现代码中,Convert.ToString方法使用toBase参数值2创建一个二进制表示。在AddSeparators扩展方法中,利用string.Join方法在每4位之后插入一个分隔符,并使用LINQ方法将数组与字符串组合。
public static class BinaryExtensions{
public static string AddSeparators(this string number)=>
number.Length <= 4 ? number : string
.Join("_",Enumerable.Range(0,number.Length/4)
.Select(i => number.Substring(i*4,4))
.ToArray());
public static string ToBinaryString(this int number)=>
Convert.ToString(number,toBase:2).AddSeparators();
}
结果显示了片段A~F的位表示,现在可以用传递给位矢量的值来验证:
BitVector32{00001001101010111100110111101111}
Section A: 1101_1110_1111
Section B: 1011_1100
Section C: 1010
Section D: 1
Section E: 1
Section F: 0