处理位的集合(特殊的集合一种)

本文介绍了C#中处理位集合的两种方式:BitArray类和BitVector32结构。BitArray类适用于不确定位数的情况,可动态调整大小,而BitVector32结构效率高,适用于已知32位的需求。通过示例展示了如何使用这两个类型进行位操作,包括设置、取反、或、与、异或等,并解释了各自的特性与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果需要处理的数字有许多位,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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值