我正在研究基于位的B / W / Greyscale预编译字体格式,并且在阅读或编写格式方面存在问题,(我无法确定问题所在。(我确实有一个基于B / W位的版本工作,但是一个别名字体看起来不太好,你可以想象,特别是在使用320x200像素屏幕时))但是我决定只使用BinaryWriter会更容易而不是在我拉动图像数据时写入bool []。
文件中像素的基本格式如下所示:
1 - 白色像素(最短,因为这将是大多数像素)
00 - 黑色像素(无理由为纯黑色像素写入10位,其中有合理的数量)
01 - 灰度像素,后跟1个字节描述像素的阴影
现在,编写所需信息的一切都很好,花花公子,因为这是全部字节,但默认的.Net 4.0 BinaryWriter将布尔值写为一个完整的字节,你可以想象,这取消了使用a基于位的格式。所以我想知道,有没有BinaryWriter,(和BinaryReader)实现那里的基于位的
编辑: 我最终创造了自己的。 (请参阅代码的答案。)
答案 0 :(得分:6)
我不相信框架中有任何内容,不,不。基本上你需要写一个类来 wrap 一个BinaryWriter
(或者只是一个流)和“到目前为止写的字节”和写入的位数。当位数达到8时,将字节写入基础流并清除。
编辑:OP发布了上述建议below的可能和可行的实施方案。
答案 1 :(得分:5)
我最后写了自己的,所以他们在这里。
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);
}
}
答案 2 :(得分:3)
如果将数据保存在字节数组中(bool没有用,占用太多空间,如果将它们用于位,它们占用内存中的一个字节)或特定{{1}的数组中适合您的数据格式。
一旦有了内部存储器表示,就不再需要基于位的二进制写入器了。您可以简单地将数据写入BinaryWriter并完成它。
...但默认的.Net 4.0 BinaryWriter将布尔值写为完整 字节,正如你可以想象的那样,否定了基于位的使用 格式....
原因是:根据定义,bool在C#中的大小为1字节。 BinaryWriter只是简单地写出你给它的东西。
答案 3 :(得分:1)
我发现自己也需要这个,所以我建立了OP并填写了所有的读/写(除了char和amp字符串,因为它们有点特殊)。
我也做了一个快速的单元测试试试看。对于仅包含布尔值(或其他自定义子字节值类型)的流,它显然便宜87.5%,而对于包含75%布尔值的随机混合流,它便宜约33%。因此对某些情况可能有用。
如果其他人需要这两个类,以下是这两个类,使用风险自负:
/// <summary>
/// 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
/// </summary>
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);
}
单元测试:
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<BBTest> pairs = new List<BBTest>();
// 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<BBTest> test = new List<BBTest>();
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();
}