C#中基于位的BinaryWriter

我正在研究基于位的B / W / Greyscale预编译字体格式,并且在读取或写入格式时遇到了问题,(我无法确定问题的位置。(我确实有基于B / W位的版本工作,但是别名字体看起来不太好,正如您可以想象的那样,尤其是在使用320×200像素屏幕时))但是决定只使用BinaryWriter比写入更容易当我拉动图像数据时,一个bool []。

文件中像素的基本格式如下所示:

1 – 白色像素(最短,因为这将是大多数像素)

00 – 黑色像素(无理由为纯黑色像素写入10位,其中有合理的数量)

01 – 灰度像素,后跟1个字节,描述像素的阴影

现在,编写所需信息的一切都很好,花花公子,因为它是全部字节,但默认的.Net 4.0 BinaryWriter将布尔值写为完整字节,并且可以想象,否定了基于位的使用格式。 所以我想知道,有没有BinaryWriter,(和BinaryReader)实现那里的基于位的

编辑:我最终创建了自己的。 (请参阅代码的答案。)

我不相信框架中有任何东西,不,不。 基本上你需要编写一个类来包装 BinaryWriter (或只是一个流)和“到目前为止写的字节”和写入的位数。 当位数达到8时,将字节写入基础流并清除。

编辑:OP发布了以下建议的可行和可行的实施方案。

我最后写了自己的,所以他们在这里。

BinaryWriter(我只覆盖了我需要的那些)

 private class BinaryWriter : System.IO.BinaryWriter { private bool[] curByte = new bool[8]; private byte curBitIndx = 0; private System.Collections.BitArray ba; public BinaryWriter(Stream s) : base(s) { } public override void Flush() { base.Write(ConvertToByte(curByte)); base.Flush(); } public override void Write(bool value) { curByte[curBitIndx] = value; curBitIndx++; if (curBitIndx == 8) { base.Write(ConvertToByte(curByte)); this.curBitIndx = 0; this.curByte = new bool[8]; } } public override void Write(byte value) { ba = new BitArray(new byte[] { value }); for (byte i = 0; i < 8; i++) { this.Write(ba[i]); } ba = null; } public override void Write(byte[] buffer) { for (int i = 0; i < buffer.Length; i++) { this.Write((byte)buffer[i]); } } public override void Write(uint value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 32; i++) { this.Write(ba[i]); } ba = null; } public override void Write(ulong value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 64; i++) { this.Write(ba[i]); } ba = null; } public override void Write(ushort value) { ba = new BitArray(BitConverter.GetBytes(value)); for (byte i = 0; i < 16; i++) { this.Write(ba[i]); } ba = null; } private static byte ConvertToByte(bool[] bools) { byte b = 0; byte bitIndex = 0; for (int i = 0; i < 8; i++) { if (bools[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } } 

而且,BinaryReader再一次,我只是覆盖了我需要的方法。

 private class BinaryReader : System.IO.BinaryReader { private bool[] curByte = new bool[8]; private byte curBitIndx = 0; private BitArray ba; public BinaryReader(Stream s) : base(s) { ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); ba = null; } public override bool ReadBoolean() { if (curBitIndx == 8) { ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); ba = null; this.curBitIndx = 0; } bool b = curByte[curBitIndx]; curBitIndx++; return b; } public override byte ReadByte() { bool[] bar = new bool[8]; byte i; for (i = 0; i < 8; i++) { bar[i] = this.ReadBoolean(); } byte b = 0; byte bitIndex = 0; for (i = 0; i < 8; i++) { if (bar[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } public override byte[] ReadBytes(int count) { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { bytes[i] = this.ReadByte(); } return bytes; } public override ushort ReadUInt16() { byte[] bytes = ReadBytes(2); return BitConverter.ToUInt16(bytes, 0); } public override uint ReadUInt32() { byte[] bytes = ReadBytes(4); return BitConverter.ToUInt32(bytes, 0); } public override ulong ReadUInt64() { byte[] bytes = ReadBytes(8); return BitConverter.ToUInt64(bytes, 0); } } 

如果将数据保存在字节数组中(bool是没有用的,占用太多空间,如果你将它们用于位,它们在内存中占用一个字节)或者在适合数据格式的特定struct的数组中。

一旦有了内部存储器表示,就不再需要基于位的二进制写入器了。 您可以简单地将数据写入BinaryWriter并完成它。

…但默认的.Net 4.0 BinaryWriter将布尔值写为完整字节,正如您可以想象的那样,否定了使用基于位的格式….

原因是:根据定义,bool在C#中的大小为1字节。 BinaryWriter只是简单地写出你给它的东西。

我发现自己也需要这个,所以我建立了OP并填写了所有的读/写(除了char和string,因为它们有点特殊)。

我也做了一个快速的unit testing试试看。 对于仅包含布尔值(或其他自定义子字节值类型)的流,它显然便宜87.5%,而对于包含75%布尔值的随机混合流,它便宜约33%。 因此对某些场景可能有用。

以下是这两个类,以防其他人需要它们,使用风险由您自行承担:

 ///  /// A binary writer that packs data into bits, to preserve space when using many bit/boolean values. Up to about 87.5% cheaper for streams that only contains boolean values. /// By: jsmars@gmail.com, based on posters classes in this post: https://stackoverflow.com/questions/7051939/bit-based-binarywriter-in-c-sharp ///  public class BinaryBitWriter : BinaryWriter { public byte BitPosition { get; private set; } = 0; private bool[] curByte = new bool[8]; private System.Collections.BitArray ba; public BinaryBitWriter(Stream s) : base(s) { } public override void Flush() { flushBitBuffer(); base.Flush(); } public override void Write(byte[] buffer, int index, int count) { for (int i = index; i < index + count; i++) Write((byte)buffer[i]); } public override void Write(byte value) { ba = new BitArray(new byte[] { value }); for (byte i = 0; i < 8; i++) Write(ba[i]); } public override void Write(bool value) { curByte[BitPosition] = value; BitPosition++; if (BitPosition == 8) flushBitBuffer(); } public override void Write(char[] chars, int index, int count) { for (int i = index; i < index + count; i++) Write(chars[i]); } public override void Write(string value) { // write strings as normal for now, so flush the bits first flushBitBuffer(); base.Write(value); } public override void Write(decimal value) { var ints = decimal.GetBits(value); for (int i = 0; i < ints.Length; i++) Write(ints[i]); } public override void Write(float value) => Write(BitConverter.GetBytes(value)); public override void Write(ulong value) => Write(BitConverter.GetBytes(value)); public override void Write(long value) => Write(BitConverter.GetBytes(value)); public override void Write(uint value) => Write(BitConverter.GetBytes(value)); public override void Write(int value) => Write(BitConverter.GetBytes(value)); public override void Write(ushort value) => Write(BitConverter.GetBytes(value)); public override void Write(short value) => Write(BitConverter.GetBytes(value)); public override void Write(double value) => Write(BitConverter.GetBytes(value)); public override void Write(char[] value) => Write(value, 0, value.Length); public override void Write(char value) { // write strings as normal for now, so flush the bits first flushBitBuffer(); base.Write(value); //var b = BitConverter.GetBytes(value); //Write(b); } public override void Write(byte[] buffer) => Write(buffer, 0, buffer.Length); public override void Write(sbyte value) => Write((byte)value); void flushBitBuffer() { if (BitPosition == 0) // Nothing to flush return; base.Write(ConvertToByte(curByte)); BitPosition = 0; curByte = new bool[8]; } private static byte ConvertToByte(bool[] bools) { byte b = 0; byte bitIndex = 0; for (int i = 0; i < 8; i++) { if (bools[i]) b |= (byte)(((byte)1) << bitIndex); bitIndex++; } return b; } } public class BinaryBitReader : BinaryReader { public byte BitPosition { get; private set; } = 8; private bool[] curByte = new bool[8]; public BinaryBitReader(Stream s) : base(s) { } public override bool ReadBoolean() { if (BitPosition == 8) { var ba = new BitArray(new byte[] { base.ReadByte() }); ba.CopyTo(curByte, 0); BitPosition = 0; } bool b = curByte[BitPosition]; BitPosition++; return b; } public override byte ReadByte() { bool[] bar = new bool[8]; byte i; for (i = 0; i < 8; i++) { bar[i] = this.ReadBoolean(); } byte b = 0; byte bitIndex = 0; for (i = 0; i < 8; i++) { if (bar[i]) { b |= (byte)(((byte)1) << bitIndex); } bitIndex++; } return b; } public override byte[] ReadBytes(int count) { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { bytes[i] = this.ReadByte(); } return bytes; } //public override int Read() => BitConverter.ToUInt64(ReadBytes(8), 0); public override int Read(byte[] buffer, int index, int count) { for (int i = index; i < index + count; i++) buffer[i] = ReadByte(); return count; // we can return this here, it will die at the above row if anything is off } public override int Read(char[] buffer, int index, int count) { for (int i = index; i < index + count; i++) buffer[i] = ReadChar(); return count; // we can return this here, it will die at the above row if anything is off } public override char ReadChar() { BitPosition = 8; return base.ReadChar(); //BitConverter.ToChar(ReadBytes(2), 0); } public override char[] ReadChars(int count) { var chars = new char[count]; Read(chars, 0, count); return chars; } public override decimal ReadDecimal() { int[] ints = new int[4]; for (int i = 0; i < ints.Length; i++) ints[i] = ReadInt32(); return new decimal(ints); } public override double ReadDouble() => BitConverter.ToDouble(ReadBytes(8), 0); public override short ReadInt16() => BitConverter.ToInt16(ReadBytes(2), 0); public override int ReadInt32() => BitConverter.ToInt32(ReadBytes(4), 0); public override long ReadInt64() => BitConverter.ToInt64(ReadBytes(8), 0); public override sbyte ReadSByte() => (sbyte)ReadByte(); public override float ReadSingle() => BitConverter.ToSingle(ReadBytes(4), 0); public override string ReadString() { BitPosition = 8; // Make sure we read a new byte when we start reading the string return base.ReadString(); } public override ushort ReadUInt16() => BitConverter.ToUInt16(ReadBytes(2), 0); public override uint ReadUInt32() => BitConverter.ToUInt32(ReadBytes(4), 0); public override ulong ReadUInt64() => BitConverter.ToUInt64(ReadBytes(8), 0); } 

unit testing:

 public static bool UnitTest() { const int testPairs = 512; var bitstream = new MemoryStream(); var bitwriter = new BinaryBitWriter(bitstream); var bitreader = new BinaryBitReader(bitstream); byte[] bytes = new byte[] { 1, 2, 3, 4, 255 }; byte Byte = 128; bool Bool = true; char[] chars = new char[] { 'a', 'b', 'c' }; string str = "hello"; var Float = 2.5f; ulong Ulong = 12345678901234567890; long Long = 1122334455667788; uint Uint = 1234567890; int Int = 999998888; ushort UShort = 12345; short Short = 4321; double Double = 9.9; char Char = 'A'; sbyte Sbyte = -128; decimal Decimal = 10000.00001m; List pairs = new List(); // Make pairs of write and read tests pairs.Add(new BBTest(Bool, (w) => w.Write(Bool), (r) => { if (r.ReadBoolean() != Bool) throw new Exception(); })); pairs.Add(new BBTest(bytes, (w) => w.Write(bytes, 0, 5), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); })); pairs.Add(new BBTest(Byte, (w) => w.Write(Byte), (r) => { if (r.ReadByte() != Byte) throw new Exception(); })); pairs.Add(new BBTest(chars, (w) => w.Write(chars, 0, 3), (r) => { if (arrayCompare(r.ReadChars(3), chars)) throw new Exception(); })); ///////////// pairs.Add(new BBTest(str, (w) => w.Write(str), (r) => { string s; if ((s = r.ReadString()) != str) throw new Exception(); })); pairs.Add(new BBTest(Decimal, (w) => w.Write(Decimal), (r) => { if (r.ReadDecimal() != Decimal) throw new Exception(); })); pairs.Add(new BBTest(Float, (w) => w.Write(Float), (r) => { if (r.ReadSingle() != Float) throw new Exception(); })); pairs.Add(new BBTest(Ulong, (w) => w.Write(Ulong), (r) => { if (r.ReadUInt64() != Ulong) throw new Exception(); })); pairs.Add(new BBTest(Long, (w) => w.Write(Long), (r) => { if (r.ReadInt64() != Long) throw new Exception(); })); pairs.Add(new BBTest(Uint, (w) => w.Write(Uint), (r) => { if (r.ReadUInt32() != Uint) throw new Exception(); })); pairs.Add(new BBTest(Int, (w) => w.Write(Int), (r) => { if (r.ReadInt32() != Int) throw new Exception(); })); pairs.Add(new BBTest(UShort, (w) => w.Write(UShort), (r) => { if (r.ReadUInt16() != UShort) throw new Exception(); })); pairs.Add(new BBTest(Short, (w) => w.Write(Short), (r) => { if (r.ReadInt16() != Short) throw new Exception(); })); pairs.Add(new BBTest(Double, (w) => w.Write(Double), (r) => { if (r.ReadDouble() != Double) throw new Exception(); })); pairs.Add(new BBTest(Char, (w) => w.Write(Char), (r) => { if (r.ReadChar() != Char) throw new Exception(); })); /////////////// pairs.Add(new BBTest(bytes, (w) => w.Write(bytes), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); })); pairs.Add(new BBTest(Sbyte, (w) => w.Write(Sbyte), (r) => { if (r.ReadSByte() != Sbyte) throw new Exception(); })); // Now add all tests, and then a bunch of randomized tests, to make sure we test lots of combinations incase there is some offsetting error List test = new List(); test.AddRange(pairs); var rnd = new Random(); for (int i = 0; i < testPairs - test.Count; i++) { if (rnd.NextDouble() < 0.75) test.Add(pairs[0]); else test.Add(pairs[rnd.Next(pairs.Count)]); } // now write all the tests for (int i = 0; i < test.Count; i++) test[i].Writer(bitwriter); bitwriter.Flush(); // now reset the stream and test to see that they are the same bitstream.Position = 0; for (int i = 0; i < test.Count; i++) test[i].ReadTest(bitreader); // As comparison, lets write the same stuff to a normal binarywriter and compare sized var binstream = new MemoryStream(); var binwriter = new BinaryWriter(binstream); for (int i = 0; i < test.Count; i++) test[i].Writer(binwriter); binwriter.Flush(); var saved = 1 - bitstream.Length / (float)binstream.Length; var result = $"BinaryBitWriter was {(saved * 100).ToString("0.00")}% cheaper than a normal BinaryWriter with random data"; bool arrayCompare(IEnumerable a, IEnumerable b) { var B = b.GetEnumerator(); B.MoveNext(); foreach (var item in a) { if (item != B.Current) return false; B.MoveNext(); } return true; } return true; } delegate void writer(BinaryWriter w); delegate void reader(BinaryReader r); class BBTest { public object Object; public writer Writer; public reader ReadTest; public BBTest(object obj, writer w, reader r) { Object = obj; Writer = w; ReadTest = r; } public override string ToString() => Object.ToString(); }