.NET 中的 BitFields 和 BitStrings
用于 BitFields 和 BitStrings 的 .NET 库
引言
本文将介绍一个 .NET 库,它实现了表示 BitString
和 BitField
的类。 BitString 是由 1 和 0 组成的字符串,在内部表示为 32 位无符号整数数组。 随着 BitString 的增长,它从左侧(最高有效位)到右侧(最低有效位)填充每个数组元素,并流入下一个数组元素。
例如,一个包含 33 个 1 的 BitString 在内部由两个 32 位无符号整数表示,如下所示
BitStrings 对于位操作非常有用。
BitField 只是一个带有命名字段/区域的 BitString。 这是通过一个内部字典实现的,该字典将字段/区域的名称与其在 BitString 中的偏移量和长度相关联。 这允许我们按名称访问 BitString 的任何字段/区域。
背景
最近,在将一些 C++ 代码转换为 C# 时,我遇到了打包结构和位字段的使用,并且惊讶地发现 C# 不提供相同的功能。 我不得不将原始 C++ 代码中的位字段使用转换为 C# 中不直观的掩码和位移操作。
我决定编写一个 C# 库,使任何此类代码转换都更轻松、更直观。 结果是一个包含 2 个主要类的库:BitString 和 BitField。
快速预览
在描述这两个类之前,让我们快速预览一下 BitField 类可以做什么。 BitField 类的一个用例如下
假设您需要创建和读取一个自定义格式的二进制文件。 假设该文件包含 12 条记录,每条记录由 5 个字节组成。 因此,每条记录长 40 位。 假设每条记录具有以下布局
字段名 | 偏移量(从0开始) | 位长度 |
---|---|---|
Light1 | 0 | 1 |
Light2 | 1 | 1 |
Fan1 | 2 | 1 |
Fan2 | 3 | 1 |
ErrorCode | 4 | 12 |
StatusCode | 16 | 4 |
保留 | 20 | 9 |
计数器 | 29 | 11 |
我们可以将文件记录定义为 BitField。 设置记录布局的代码使用 BitField 如下所示
// Define a BitField structure of 40-bits (5 Bytes) BitField bf = new BitField(); bf.AddField("Light1", 1); bf.AddField("Light2", 1); bf.AddField("Fan1", 1); bf.AddField("Fan2", 1); bf.AddField("ErrorCode", 12); bf.AddField("StatusCode", 4); bf.AddField("Reserved", 9); bf.AddField("Counter", 11); // After defining structure, we must initialize it. This // call will initialize the 40-bit BitString to all 0's bf.InitBitField();
我们可以通过以下代码轻松设置 BitField 中任何字段/区域的值
// Initialize some fields to some value. If we don't // explicitly set a value, the field is all 0's from the // earlier call to InitBitField(). Note we can use // strings or integers for assignment. bf["Light1"] = 1; bf["Fan2"] = "1"; bf["ErrorCode"] = "111100001111"; bf["StatusCode"] = 2;
现在,我们可以通过以下代码将记录写入文件
// Create the binary file name string fn = Path.GetTempFileName(); // Write out 12 records where each record is represented by // a BitField using (FileStream fs = new FileStream(fn, FileMode.Append)) { for (int i = 0; i < 12; i++) { // Convert the BitField into a byte array byte[] temp = bf.GetBytes(); for (int j = 0; j < temp.Length; j++) { // Write out the BitField/record fs.WriteByte(temp[j]); } // Increment the BitField's Counter value by 1 // using the BitString's increment operator ++bf["Counter"]; } }
我们也可以使用同一个 BitField 对象将二进制文件读回。 为此,我们只需从文件中读取一条记录,然后用它初始化 BitField(通过调用 InitBitField()
)。 现在我们可以轻松地从记录中访问每个单独的字段。
// Now we want to read in the binary file that we just created! List<string> report = new List<string>(); using (BinaryReader BinReader = new BinaryReader(File.Open(fn, FileMode.Open))) { for (int i = 0; i < 12; i++) { // Read each 5-byte (40-bit) record into an array of bytes byte[] buffer = BinReader.ReadBytes(5); // Initialize the BitField with the record from the binary file bf.InitBitField(buffer); // Extract each field for display in a report string temp = "BitField = " + bf.ToString() + Environment.NewLine; temp = temp + "Light1 = " + bf["Light1"].ToString(); temp = temp + ", Light2 = " + bf["Light2"].ToString(); temp = temp + ", Fan1 = " + bf["Fan1"].ToString(); temp = temp + ", Fan2 = " + bf["Fan2"].ToString() + Environment.NewLine; temp = temp + "ErrorCode = " + bf["ErrorCode"].ToString(); temp = temp + ", StatusCode = " + bf["StatusCode"].ToString(); temp = temp + ", Reserved = " + bf["Reserved"].ToString(); temp = temp + ", Counter = " + bf["Counter"].ToString(); temp = temp + Environment.NewLine + Environment.NewLine; report.Add(temp); } }
如果我们打印报告的内容,它将是
BitField = 1001111100001111001000000000000000000000
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000000
BitField = 1001111100001111001000000000000000000001
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000001
BitField = 1001111100001111001000000000000000000010
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000010
BitField = 1001111100001111001000000000000000000011
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000011
BitField = 1001111100001111001000000000000000000100
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000100
BitField = 1001111100001111001000000000000000000101
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000101
BitField = 1001111100001111001000000000000000000110
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000110
BitField = 1001111100001111001000000000000000000111
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000000111
BitField = 1001111100001111001000000000000000001000
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000001000
BitField = 1001111100001111001000000000000000001001
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000001001
BitField = 1001111100001111001000000000000000001010
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 00000001010
BitField = 1001111100001111001000000000000000001011
Light1 = 1, Light2 = 0, Fan1 = 0, Fan2 = 1
ErrorCode = 111100001111, StatusCode = 0010, Reserved = 000000000, Counter = 0000000101
BitString 要求
BitString 类是库中两个类的工作核心。 如前所述,BitStrings 是由 1 和 0 组成的字符串。 例如,下面的 BitString 有 4 位
1011
在设计过程中,我希望 BitString 类支持特定的功能。 我希望 BitString 类支持大多数人期望能够对 BitStrings 执行的常规操作。
我希望能够连接两个 BitStrings
0101 + 111100 = 0101111100
我希望能够对两个 BitStrings 执行常规的按位操作
0101 AND 1111 = 0101 0101 OR 1111 = 1111 0101 XOR 1111 = 1010
如果 BitStrings 的长度不相等,则较短的字符串将在右侧填充 1 或 0,以便结果 BitString 具有所需的位,其中两个 BitStrings 匹配,其余位匹配较长的 BitString
010 AND 101111101 = 000111101 010 OR 101111101 = 111111101 010 XOR 101111101 = 111111101
我希望能够翻转 BitString 中的所有位
0101 => 1010 (Flip)
我希望能够反转 BitStrings 中的位
111001 => 100111 (Reverse)
我希望能够像位代表无符号整数一样递增/递减 BitStrings
0111 => 1000 (Increment) 1000 => 0111 (Decrement)
我希望能够将 BitString 转换为其带符号/无符号整数表示
1000 => 8 (Unsigned integer) 1000 => -8 (2's Complement Signed integer)
我希望能够对 BitString 进行子集操作
111010000 => 010 (Subset a BitString from location 3 to location 5)
我希望能够设置/清除 BitString 中的单个位。
111000 => 101000 (Clear bit 1) 111000 => 111010 (Set bit 4)
最后,我希望能够处理几乎任何长度的 BitStrings。 对于这个要求,我希望 BitString 在内部由无符号 32 位整数数组表示,因此 BitString 101011110101 将在内部存储为
uint[0] = 10101111010100000000000000000000
我希望 BitString 被左对齐填充到每个数组元素中,并且随着 BitString 的增长,它应该溢出到下一个数组元素。 因此,一个包含 33 个全 1 的 BitString 将存储为
uint[0] = 11111111111111111111111111111111 uint[1] = 10000000000000000000000000000000
我希望能够获取内部缓冲区的字节数组表示,因此上面的 uint 数组将作为以下字节数组返回
byte[0] = 11111111 byte[1] = 11111111 byte[2] = 11111111 byte[3] = 11111111 byte[4] = 10000000
字节数组表示使我们能够轻松地将 BitString 的原始位写入流。
BitField 要求
BitField 只是一个带有命名字段/区域的 BitString。 这意味着我希望能够将 BitString 中的一组连续位与一个名称相关联。 BitField 的要求很简单。
- 我希望有一种方法可以为 BitString 中的一组连续位赋予唯一的名称。
- 我希望能够通过其唯一名称引用任何此类组。
类设计
库的类图如上所示。 此库中的两个主要类是 BitString 类和 BitField 类。 大部分工作由 BitString 类完成。 BitField 类包含一个 BitString 对象 (m_BitString
) 和一个字典 (m_Fields
),该字典将字段/区域的名称映射到该区域在 BitString 中的位置和长度。 该字典提供了访问 BitString 中任何命名字段/区域的能力。
使用代码
要在您的代码中使用该库,只需在项目中添加对该库 (BitString.dll
) 的引用,并为您要使用的命名空间 "GCore
" 添加 using 语句。 现在您应该可以在项目中使用了 BitString 和 BitField 类。
以下是一个简单的程序,展示了如何使用 BitString 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GCore;
namespace DemoConsole
{
class Program
{
static void Main(string[] args)
{
// Instantiating a BitString using a regular string of 1's and 0's
String bits1 = "101010";
BitString bs1 = new BitString(bits1);
// bs1 = 101010
Console.WriteLine("bs1 = " + bs1.ToString());
// You can also just use assignment with a string value to set the BitString
bs1 = "11110000";
Console.WriteLine("bs1 = " + bs1.ToString());
// Instantiating a BitString using another BitString
BitString bs2 = new BitString(bs1);
// bs2 = 11110000
Console.WriteLine("bs2 = " + bs2.ToString());
// We can flip all the bits
bs2.Flip();
// bs2 = 00001111
Console.WriteLine("bs2 (flipped) = " + bs2.ToString());
// You can bitwise XOR two BitStrings
BitString temp = bs1 ^ bs2;
// bs1 ^ bs2 = 11111111
Console.WriteLine("bs1 ^ bs2 = " + temp.ToString());
// You can concatenate two BitStrings
BitString bs3 = bs1 + bs2;
// bs3 (bs1 + bs2) = 1111000000001111
Console.WriteLine("bs3 (bs1 + bs2) = " + bs3.ToString());
// Instantiating a BitString using an array of bytes
byte[] seed = new byte[2];
seed[0] = 0xFF;
seed[1] = 0xAA;
bs3 = new BitString(seed);
// bs3 = 1111111110101010
Console.WriteLine("bs3 = " + bs3.ToString());
// You can reverse a BitString
bs3.Reverse();
// bs3 = 0101010111111111
Console.WriteLine("bs3 (reverse) = " + bs3.ToString());
// Setting a BitString's value by using a integer
BitString bs4 = 4;
// bs4 = 0000000000000000000000000100
Console.WriteLine("bs4 = " + bs4.ToString());
// You can subset a BitString
BitString bs5 = bs4[29, 3];
// bs5 = 100
Console.WriteLine("bs5 = " + bs5.ToString());
// You can increment a BitString
++bs5;
// bs5 = 101
Console.WriteLine("++bs5 = " + bs5.ToString());
// Pause and wait for key stroke to end
Console.ReadKey();
}
}
}
Visual Studio 解决方案
我在本文中包含了该库的 Visual Studio 2015 解决方案。 该解决方案包含 4 个项目
- BitString 项目 (此项目是 BitString/BitField 库)
- BitString 测试项目 (此项目包含库的单元测试)
- 演示项目 (此项目包含一个允许您与 BitStrings 交互并查看其内部属性的小型演示)
- DemoConsole 项目 (此项目包含前面显示的 BitString 的简单控制台程序)
Demo 项目提供了一个 GUI,允许您玩弄 BitStrings 并查看它们的内部表示。 标有“Binary File Test”的按钮运行了“Quick Preview”部分中显示的程序。 标有“Benchmark”的按钮使用子集运算符将一个 6 位 BitString 在一个 64 位 BitString 中随机位置插入 10,000,000 次。 在我的机器(AMD Phenom II X6 1035T)上,大约需要 1 分 30 秒才能完成。
我希望这个库对某些人有用。 它可以处理非常长的 BitStrings/BitFields,并且 BitStrings 不受字节/字边界的限制。 它可能不够快,无法进行实时位操作,但我希望它能使编写需要进行位操作的 .NET 代码更轻松、更直观。
感谢阅读!
历史
- 2016/10/19 - 根据读者的优化建议(irneb)更新了 BitString 库。 这将某些操作的速度提高了 25%。 添加了 Randomize() 方法以随机化 BitString 中的位。
- 2016/10/08 - 初始发布。