Verwenden Sie C#, um Bitstrom-basierte Daten zu verarbeiten
0x00 Ursache
In letzter Zeit müssen wir einige Bitstrom-basierte Daten verarbeiten. Computerverarbeitungsdaten erfolgen im Allgemeinen in Bytes (8 Bit). Dasselbe gilt für Daten, die mit BinaryReader gelesen werden. Auch wenn der Bool-Typ gelesen wird, handelt es sich immer noch um ein Byte. Mit Hilfe einiger in der C#-Basisklassenbibliothek bereitgestellter Methoden können jedoch auch bitbasierte Daten gelesen werden. Nachdem ich die Aufgabe erledigt hatte, fand ich, dass bitbasierte Daten sehr interessant sind, also habe ich versucht, 7-Bit- und 6-Bit-Kodierung zu verwenden, um gängige ASCII-Zeichen zu kodieren. Abschließend werde ich als Blog etwas Neues schreiben. Einerseits wird es ein Rekord sein, andererseits hoffe ich, dass es für Gärtner mit ähnlichen Bedürfnissen hilfreich sein wird.
0x01 Lesen von Bitstromdaten
Angenommen, wir haben ein Byte b = 35 und wir müssen die ersten 4 Bits und die letzten 4 Bits jeweils in zwei Zahlen lesen, was sollen wir dann tun? tun? Tun Sie es. Obwohl es in der Basisklassenbibliothek keine vorgefertigte Methode gibt, kann dies mithilfe von Binärzeichenfolgen in zwei Schritten erfolgen.
1. Stellen Sie b als Binärzeichenfolge 00100011 dar
2. Konvertieren Sie die 4 Bits davor und danach in Zahlen:
Convert.ToInt32("0010");
Dies So funktioniert es. Das Lesen der Daten erfolgt bitbasiert.
Im ersten Schritt gibt es viele Möglichkeiten, Bytes in Binärzeichenfolgen umzuwandeln,
1. Der einfachste Weg: Convert.ToString(b,2). Wenn nicht genügend 8 Bits vorhanden sind, fügen Sie 0 in den oberen Bits hinzu.
2. Sie können das Byte auch mit 1,2,4,8...128 verknüpfen und die Bits von niedrig nach hoch herausnehmen.
3. Sie können auch eine UND-Verknüpfung mit Byte und 32 durchführen, dann das Byte nach links verschieben und erneut eine UND-Verknüpfung mit 128 durchführen.
Die erste Methode generiert eine große Anzahl von String-Objekten. Ich habe keinen großen Unterschied zur zweiten und dritten Methode festgestellt. Ich habe mich rein nach meinem Gefühl für drei entschieden. Der Code lautet wie folgt:
public static char[] ByteToBinString(byte b) { var result = new char[8]; for (int i = 0; i < 8; i++) { var temp = b & 128; result[i] = temp == 0 ? '0' : '1'; b = (byte)(b << 1); } return result; }
Um Byte[] in eine Binärzeichenfolge umzuwandeln, können Sie
Public string BitReader(byte[] data) { BinString = new StringBuilder(data.Length * 8); for (int i = 0; i < data.Length; { BinString.Append(ByteToBinString(data[i])); } return BinString.ToString(); }
Auf diese Weise werden die Byte[]-Daten abgerufen , kann in eine Binärzeichenfolge umgewandelt werden. Die Zeichenfolge wird gespeichert, die Binärzeichenfolge wird entsprechend der Offset-Bitposition und der Bitlänge daraus gelesen und in bool, Int16, Int32 usw. konvertiert. Basierend auf dieser Idee können Sie eine BitReader-Klasse schreiben, die StringBuilder zum Speichern von Binärzeichenfolgen verwendet und eine Read-Methode zum Lesen von Daten aus Binärzeichenfolgen bereitstellt. Um den Datenfluss besser bewältigen zu können, wird eine Position hinzugefügt, um den aktuellen Offset aufzuzeichnen. Wenn bestimmte Lesemethoden zum Lesen von Daten verwendet werden, wird die Position ebenfalls entsprechend verschoben. Wenn Sie beispielsweise ReadInt16 zum Lesen von Daten verwenden, liest BitReader 16 Bits von der aktuellen Position von Position, wandelt sie in Int16 um und gibt sie zurück. Gleichzeitig bewegt sich Position um 16 Bits rückwärts. Die Unterscheidung besteht darin, dass sich die Position beim direkten Lesen von der aktuellen Position nicht verschiebt, wenn die Startoffsetposition angegeben werden muss. Der Teil des BitReader-Klassencodes lautet wie folgt:
public class BitReader { public readonly StringBuilder BinString; public int Position { get; set; } public BitReader(byte[] data) { BinString = new StringBuilder(data.Length * 8); for (int i = 0; i < data.Length; i++) { BinString.Append(ByteToBinString(data[i])); } Position = 0; } public byte ReadByte(int offset) { var bin = BinString.ToString(offset, 8); return Convert.ToByte(bin, 2); } public byte ReadByte() { var result = ReadByte(Position); Position += 8; return result; } public int ReadInt(int offset, int bitLength) { var bin = BinString.ToString(offset, bitLength); return Convert.ToInt32(bin, 2); } public int ReadInt(int bitLength) { var result = ReadInt(Position, bitLength); Position += bitLength; return result; } public static char[] ByteToBinString(byte b) { var result = new char[8]; for (int i = 0; i < 8; i++) { var temp = b & 128; result[i] = temp == 0 ? '0' : '1'; b = (byte)(b << 1); } return result; } }
BitReader wie folgt verwenden: 4bit liest Daten aus byte[] buff= {35,12} wie folgt:
var reader = new BitReader(buff); //二进制字符串为0010001100001100 var num1 = reader.ReadInt(4); //从当前Position读取4bit为int,Position移动4bit,结果为2,当前Position=4 var num2 = reader.ReadInt(5,6); //从偏移为5bit的位置读取6bit为int,Position不移动,结果为48,当前Position=4 var b = reader.ReadBool(); //从当前Position读取1bit为bool,Position移动1bit,结果为False,当前Position=5
0x02 Schreiben von Bitstromdaten
Schreiben Im umgekehrten Prozess verwenden wir die BitWriter-Klasse, in der der StringBuilder zum Speichern der Binärzeichenfolge gespeichert ist. Beim Schreiben von Daten müssen Sie die Daten übergeben und die Anzahl der erforderlichen Bits angeben um diese Daten zu speichern. Nach Abschluss des Schreibvorgangs kann die in StringBuilder gespeicherte Binärzeichenfolge entsprechend 8 Bit in Byte [] umgewandelt und zurückgegeben werden. Der Kernteil von BitWriter ist wie folgt:
public class BitWriter { public readonly StringBuilder BinString; public BitWriter() { BinString = new StringBuilder(); } public BitWriter(int bitLength) { var add = 8 - bitLength % 8; BinString = new StringBuilder(bitLength + add); } public void WriteByte(byte b, int bitLength=8) { var bin = Convert.ToString(b, 2); AppendBinString(bin, bitLength); } public void WriteInt(int i, int bitLength) { var bin = Convert.ToString(i, 2); AppendBinString(bin, bitLength); } public void WriteChar7(char c) { var b = Convert.ToByte(c); var bin = Convert.ToString(b, 2); AppendBinString(bin, 7); } public byte[] GetBytes() { Check8(); var len = BinString.Length / 8; var result = new byte[len]; for (int i = 0; i < len; i++) { var bits = BinString.ToString(i * 8, 8); result[i] = Convert.ToByte(bits, 2); } return result; } public string GetBinString() { Check8(); return BinString.ToString(); } private void AppendBinString(string bin, int bitLength) { if (bin.Length > bitLength) throw new Exception("len is too short"); var add = bitLength - bin.Length; for (int i = 0; i < add; i++) { BinString.Append('0'); } BinString.Append(bin); } private void Check8() { var add = 8 - BinString.Length % 8; for (int i = 0; i < add; i++) { BinString.Append("0"); } } }
Hier ist ein einfaches Beispiel:
var writer = new BitWriter(); writer.Write(12,5); //把12用5bit写入,此时二进制字符串为:01100 writer.Write(8,16); //把8用16bit写入,此时二进制字符串为:011000000000000001000 var result = writer.GetBytes(); //8bit对齐为011000000000000001000000 //返回结果为[96,0,64]
0x03 7-Bit-Zeichenkodierung
Unsere häufig verwendeten ASCII-Zeichen Verwenden Sie 8-Bit-Codierung, aber die Zeichen, die wirklich häufig verwendet werden, sind nur 7 Bit, und das höchste Bit ist 0, sodass wir für einen englischen Artikel 7 Bit zum Neucodieren verwenden können, ohne Informationen zu verlieren. Der Codierungsprozess besteht darin, die Artikelzeichen einzeln herauszunehmen, sie mit BitWriter in 7 Bit zu schreiben und schließlich das neu codierte Byte [] zu erhalten. Um korrekt lesen zu können, legen wir fest, dass beim Lesen der 8-Bit-Daten 2 der Anfang der Daten bedeutet und die nächsten 16-Bit-Daten die Anzahl der nachfolgenden Zeichen darstellen. Der Code lautet wie folgt:
public byte[] Encode(string text) { var len = text.Length * 7 + 24; var writer = new BitWriter(len); writer.WriteByte(2); writer.WriteInt(text.Length, 16); for (int i = 0; i < text.Length; i++) { var b = Convert.ToByte(text[i]); writer.WriteByte(b, 7); } return writer.GetBytes(); }
Wenn wir Daten auf ähnliche Weise lesen, suchen wir zuerst nach der Startkennung, lesen dann die Anzahl der Zeichen aus und lesen die Zeichen der Reihe nach entsprechend der Anzahl der Zeichen Der Code lautet wie folgt:
public string Decode(byte[] data) { var reader = new BitReader(data); while (reader.Remain > 8) { var start = reader.ReadByte(); if (start == 2) break; } var len = reader.ReadInt(16); var result = new StringBuilder(len); for (int i = 0; i < len; i++) { var b = reader.ReadInt(7); var ch = Convert.ToChar(b); result.Append(ch); } return result.ToString(); }
Aufgrund des Vorhandenseins des Datenheaders werden die codierten Daten bei der Codierung einiger Zeichen länger
不过随着字符越多,编码后节省的越多。
0x04 6比特字符编码
从节省数据量的角度,如果允许损失部分信息,例如损失掉字母大小写,是可以进一步减少编码所需比特数的。26个字母+10个数字+符号,可以用6bit(64)进行编码。不过使用这种编码方式就不能用ASCII的映射方式了,我们可以自定义映射,例如0-10映射为十个数字等等,也可以使用自定义的字典,也就是传说中的密码本。经常看国产谍战片的应该都知道密码本吧,密码本就是一个字典,把字符进行重新映射获取明文,算是简单的单码替代,加密强度很小,在获取足量数据样本后基于统计很容易就能破解。下面我们就尝试基于自定义字典用6bit重新编码。
编码过程:
仍然像7bit编码那样写入消息头,然后依次取出文本中的字符,从字典中找到对应的数字,把数字按照6bit长度写入到BitWriter
public byte[] Encode(string text) { text = text.ToUpper(); var len = text.Length * 6 + 24; var writer = new BitWriter(len); writer.WriteByte(2); writer.WriteInt(text.Length, 16); for (int i = 0; i < text.Length; i++) { var index = GetChar6Index(text[i]); writer.WriteInt(index, 6); } return writer.GetBytes(); } private int GetChar6Index(char c) { for (int i = 0; i < 64; i++) { if (Dict.Custom[i] == c) return i; } return 10; //return * }
解码过程:
解码也很简单,找到消息头,依次按照6bit读取数据,并从字典中找到对应的字符:
public string Decode(byte[] data) { var reader = new BitReader(data); while(reader.Remain > 8) { var start = reader.ReadByte(); if (start == 2) break; } var len = reader.ReadInt(16); var result = new StringBuilder(len); for (int i = 0; i < len; i++) { var index = reader.ReadInt(6); var ch = Dict.Custom[index]; result.Append(ch); } return result.ToString(); }
同样一段文本用6bit自定义字典编码后数据长度更短了,不过损失了大小写和换行等格式。
如果从加密的角度考虑,可以设置N个自定义字典(假设10个),在消息头中用M bit(例如4bit)表示所用的字典。这样在每次编码时随机选择一个字典编码,解码时根据4bit数据选择相应字典解码,并且定时更换字典可以增大破解难度。感兴趣的园友可以自行尝试。
0x05 写在最后
以上是我处理比特流数据的一点心得,仅仅是我自己能想到的一种方法,满足了我的需求。如果有更效率的更合理的方法,希望赐教。另外编码和解码的两个例子是出于有趣写着玩的,在实际中估计也用不到。毕竟现在带宽这么富裕,数据加密也有N种可靠的多的方式。
示例代码:https://github.com/durow/TestArea/tree/master/BitStream