12.2 处理位

如果需要处理的数字有许多位,就可以使用BitArray类和BitVector32结构。BitArray类位于名称空间System.Collections中,BitVector32结构位于名称空间System.Collections.Specialized中。这两种类型最重要的区别是,BitArray类可以重新设置大小,如果事先不知道需要的位数,就可以使用BitArray类,它可以包含非常多的位。BitVector32结构是基于栈的,因此比较快。BitVector32结构仅包含32位,它们存储在一个整数中。

12.2.1 BitArray类

BitArray类是一个引用类型,它包含一个int数组,其中每32位使用一个新整数。这个类的成员如表12-1所示。

表12-1

辅助方法DisplayBits()遍历BitArray,根据位的设置情况,在控制台上显示1或0(代码文件BitArraySample/Program.cs):

        public static void DisplayBits(BitArray bits)
        {
          foreach (bool bit in bits)
          {
            Write(bit ? 1: 0);
          }
        }

BitArraySample使用如下依赖项和名称空间:

依赖项:

        NETStandard.Library

名称空间:

        System
        System.Collections
        static System.Console

说明BitArray类的示例创建了一个包含8位的数组,其索引是0~7。SetAll()方法把这8位都设置为true。接着Set()方法把对应于1的位设置为false。除了Set()方法之外,还可以使用索引器,例如,下面的第5个和第7个索引:

        var bits1 = new BitArray(8);
        bits1.SetAll(true);
        bits1.Set(1, false);
        bits1[5] = false;
        bits1[7] = false;
        Write("initialized: ");
        DisplayBits(bits1);
        WriteLine();

这是初始化位的显示结果:

        initialized: 10111010

Not()方法会对BitArray类的位取反:

        Write(" not ");
        DisplayBits(bits1);
        bits1.Not();
        Write(" = ");
        DisplayBits(bits1);
        WriteLine();

Not()方法的结果是对所有的位取反。如果某位是true,则执行Not()方法的结果就是false,反之亦然。

        not 10111010 = 01000101

这里创建了一个新的BitArray类。在构造函数中,因为使用变量bits1初始化数组,所以新数组与旧数组有相同的值。接着把第0、1和4位的值设置为不同的值。在使用Or()方法之前,显示位数组bits1和bits2。Or()方法将改变bits1的值:

        var bits2 = new BitArray(bits1);
        bits2[0] = true;
        bits2[1] = false;
        bits2[4] = true;
        DisplayBits(bits1);
        Write(" or ");
        DisplayBits(bits2);
        Write(" = ");
        bits1.Or(bits2);
        DisplayBits(bits1);
        WriteLine();

使用Or()方法时,从两个输入数组中提取设置位。结果是,如果某位在第一个或第二个数组中设置为true,该位在执行Or()方法后就是true:

        01000101 or 10001101 = 11001101

下面使用And()方法作用于位数组bits1和bits2:

        DisplayBits(bits2);
        Write(" and ");
        DisplayBits(bits1);
        Write(" = ");
        bits2.And(bits1);
        DisplayBits(bits2);
        WriteLine();

And()方法只把在两个输入数组中都设置为true的位设置为true:

        10001101 and 11001101 = 10001101

最后使用Xor()方法进行异或操作:

        DisplayBits(bits1);
        Write(" xor ");
        DisplayBits(bits2);
        bits1.Xor(bits2);
        Write(" = ");
        DisplayBits(bits1);
        WriteLine();

使用Xor()方法,只有一个(不能是两个)输入数组的位设置为1,结果位才是1。

        11001101 xor 10001101 = 01000000

12.2.2 BitVector32结构

如果事先知道需要的位数,就可以使用BitVector32结构替代BitArray类。BitVector32结构效率较高,因为它是一个值类型,在整数栈上存储位。一个整数可以存储32位。如果需要更多的位,就可以使用多个BitVector32值或BitArray类。BitArray类可以根据需要增大,但BitVector32结构不能。

表12-2列出了BitVector32结构中与BitArray类完全不同的成员。

表12-2

BitVectorSample使用如下依赖项和名称空间:

依赖项:

        NETStandard.Library
        System.Collections.Specialized

名称空间:

        System.Collections.Specialized
        System.Text
        static System.Console

示例代码用默认构造函数创建了一个BitVector32结构,其中所有的32位都初始化为false。接着创建掩码,以访问位矢量中的位。对CreateMask()方法的第一个调用创建了用来访问第一位的一个掩码。调用CreateMask()方法后,bit1被设置为1。再次调用CreateMask()方法,把第一个掩码作为参数传递给CreateMask()方法,返回用来访问第二位(它是2)的一个掩码。接着,将bit3设置为4,以访问位编号3。bit4的值是8,以访问位编号4。

然后,使用掩码和索引器访问位矢量中的位,并相应地设置字段(代码文件BitArraySample/Program.cs):

        var bits1 = new BitVector32();
        int bit1 = BitVector32.CreateMask();
        int bit2 = BitVector32.CreateMask(bit1);
        int bit3 = BitVector32.CreateMask(bit2);
        int bit4 = BitVector32.CreateMask(bit3);
        int bit5 = BitVector32.CreateMask(bit4);
        bits1[bit1] = true;
        bits1[bit2] = false;
        bits1[bit3] = true;
        bits1[bit4] = true;
        bits1[bit5] = true;
        WriteLine(bits1);

BitVector32结构有一个重写的ToString()方法,它不仅显示类名,还显示1或0,来说明位是否设置了,如下所示:

        BitVector32{00000000000000000000000000011101}

除了用CreateMask()方法创建掩码之外,还可以自己定义掩码,也可以一次设置多位。十六进制值abcdef与二进制值101010111100110111101111相同。用这个值定义的所有位都设置了:

        bits1[0xabcdef] = true;
        WriteLine(bits1);

在输出中可以验证设置的位:

        BitVector32{00000000101010111100110111101111}

把32位分别放在不同的片段中非常有用。例如,IPv4地址定义为一个4字节的数,该数存储在一个整数中。可以定义4个片段,把这个整数拆分开。在多播IP消息中,使用了几个32位的值。其中一个32位的值放在这些片段中:16位表示源号,8位表示查询器的查询内部码,3位表示查询器的健壮变量,1位表示抑制标志,还有4个保留位。也可以定义自己的位含义,以节省内存。

下面的例子模拟接收到值0x79abcdef,把这个值传送给BitVector32结构的构造函数,从而相应地设置位:

        int received = 0x79abcdef;
        BitVector32 bits2 = new BitVector32(received);
        WriteLine(bits2);

在控制台上显示了初始化的位:

        BitVector32{01111001101010111100110111101111}

接着创建6个片段。第一个片段需要12位,由十六进制值0xfff定义(设置了12位)。片段B需要8位,片段C需要4位,片段D和E需要3位,片段F需要两位。第一次调用CreateSection()方法只是接收0xfff,为最前面的12位分配内存。第二次调用CreateSection()方法时,将第一个片段作为参数传递,从而使下一个片段从第一个片段的结尾处开始。CreateSection()方法返回一个BitVector32. Section类型的值,该类型包含了该片段的偏移量和掩码。

        // sections: FF EEE DDD CCCC BBBBBBBB
        // AAAAAAAAAAAA
        BitVector32.Section sectionA = BitVector32.CreateSection(0xfff);
        BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA);
        BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB);
        BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC);
        BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD);
        BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE);

把一个BitVector32.Section类型的值传递给BitVector32结构的索引器,会返回一个int,它映射到位矢量的片段上。这里使用一个帮助方法IntToBinaryString(),获得该int数的字符串表示:

        WriteLine($"Section A: {IntToBinaryString(bits2[sectionA], true)}");
        WriteLine($"Section B: {IntToBinaryString(bits2[sectionB], true)}");
        WriteLine($"Section C: {IntToBinaryString(bits2[sectionC], true)}");
        WriteLine($"Section D: {IntToBinaryString(bits2[sectionD], true)}");
        WriteLine($"Section E: {IntToBinaryString(bits2[sectionE], true)}");
        WriteLine($"Section F: {IntToBinaryString(bits2[sectionF], true)}");

IntToBinaryString()方法接收整数中的位,并返回一个包含0和1的字符串表示。在实现代码中遍历整数的32位。在迭代过程中,如果该位设置为1,就在StringBuilder的后面追加1,否则,就追加0。在循环中,移动一位,以检查是否设置了下一位。

        public static string IntToBinaryString(int bits, bool removeTrailingZero)
        {
          var sb = new StringBuilder(32);
          for (int i = 0; i < 32; i++)
          {
            if ((bits & 0x80000000) ! = 0)
            {
              sb.Append("1");
            }
            else
            {
              sb.Append("0");
            }
            bits = bits << 1;
          }
          string s = sb.ToString();
          if (removeTrailingZero)
          {
            return s.TrimStart('0');
          }
          else
          {
            return s;
          }
        }

结果显示了片段A~F的位表示,现在可以用传递给位矢量的值来验证:

        Section A: 110111101111
        Section B: 10111100
        Section C: 1010
        Section D: 1
        Section E: 111
        Section F: 1