年初研究了下GIF文件格式,最近又研究了下Flash文件格式,在对文件格式进行封装的时候经常遇到很多"不规则"按bit读取的情况,而.NET框架里的流最小单位只能读到byte读取,反反复复的写些Byte转换代码实在是烦人.
而且在研究Flash格式时发现,Flash里有些结构体的信息是不定长的,它有一个由几个bit表示的长度信息,用于表示后续的每个信息的bit长度,这种不定长的结构体减小的SWF文件的大小,但是也让我在封装SWF文件格式类库时有些郁闷.
先哪个例子看看,以下是Flash中矩形区域结构体的定义:
RECT
Field Type Comment
Nbits UB[5] Bits in each rect value field
Xmin SB[Nbits] x minimum position for rect
Xmax SB[Nbits] x maximum position for rect
Ymin SB[Nbits] y minimum position for rect
Ymax SB[Nbits] y maximum position for rect
注:UB表示无符号的不定长bit值,SB表示不定长的有符号bit值
由上述定义可以看出Nbits定义了后续的值占用的bit数,而Nbits自身占用5个bit.
举个Flash文件格式文档里的例子,假设一个RECT结构体的信息如下表示:
78 00 05 5F 00 00 0F A0 00
翻译成二进制是:
0111 1000 0000 0000 0000 0101 0101 1111 0000 0000
0000 0000 0000 1111 1010 0000 0000 0000
前5个bit取出来就是:01111 -> 15
说明后续每个值占用15个bit,每个值取出来如下:
000000000000000 -> 0 = Xmin
010101011111000 -> 11000 = Xmax
000000000000000 -> 0 = Ymin
001111101000000 -> 8000 = Ymax
这个过程我在代码里怎么处理的呢?
原先(以前)的做法如下:
写了一个BytesToBin函数,把读取的Byte数组转为二进制值,这个二进制值其实是以01组成的字符串.
代码如下:
public static string BytesToBin(params byte[] bytes)

{
StringBuilder _result = new StringBuilder();

foreach (byte data in bytes)

{
byte _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0,_doc % 2);
_doc = (byte)(_doc / 2);
}

_result.Append(_newStr.ToString().PadLeft(8, '0'));
}
else

{
_result.Append("00000000");
}
}

return _result.ToString();
}
然后我使用Convert.ToInt32函数将字符串中间的几位截取并转为int值.
具体代码如下:
public void Load(BinaryReader binReader)

{
Nbits = (byte)(binReader.PeekChar() >> 3);

int _bitCount = Nbits * 4 + 5;
int _byteCount = _bitCount / 8;

if (_bitCount % 8 != 0) _byteCount++;

byte[] _rectInfo = binReader.ReadBytes(_byteCount);

string _bitStr = Common.Math.BytesToBin(_rectInfo);

Xmin = Convert.ToInt32(_bitStr.Substring(5, Nbits), 2);
Xmax = Convert.ToInt32(_bitStr.Substring(Nbits + 5, Nbits), 2);
Ymin = Convert.ToInt32(_bitStr.Substring(Nbits * 2 + 5, Nbits), 2);
Ymax = Convert.ToInt32(_bitStr.Substring(Nbits * 3 + 5, Nbits), 2);
}

这样做看起来还可以,但是好像效率不高.
于是我写了以下两个函数,用于在从byte数组中截取所需要的中间几位的值

查看详细代码
public static UInt64 GetValueFromBits(byte[] bits, int start, int end)

{
if (bits == null || bits.Length == 0
|| start < 0 || end > bits.Length - 1 || start > end)
return 0;

UInt64 _value = 0;
int _length = 0;
int _valueLength = end - start + 1;

int _byteIndex = 0;
byte _byteValue = 0;
UInt64 _tempValue = 0;

for (int i = 0; i < bits.Length; i++)

{
_byteIndex = i * 8;

if (_byteIndex > end) break;
if (_byteIndex + 7 < start) continue;

_byteValue = bits[i];

int _byteStart = start - _byteIndex;
int _byteEnd = end - _byteIndex;

if (_byteStart < 0) _byteStart = 0;

if (_byteEnd > 7) _byteEnd = 7;
else if (_byteEnd < 0) _byteEnd = _byteIndex - end;

_byteValue = (byte)(_byteValue << _byteStart);
_byteValue = (byte)(_byteValue >> _byteStart);
_byteValue = (byte)(_byteValue >> (7 - _byteEnd));

_length += _byteEnd - _byteStart + 1;

_tempValue = _byteValue;

_tempValue = _tempValue << (_valueLength - _length);

_value = _value | _tempValue;
}

return _value;
}

public static UInt64[] GetValuesFromBits(byte[] bits, int start, int subLength, int valueCount)

{
if (bits == null || bits.Length == 0
|| start < 0 || valueCount <= 0 || subLength <= 0
|| start + subLength * valueCount > bits.Length)
return null;

UInt64[] _values = new UInt64[valueCount];

for (int i = 0; i < valueCount; i++)

{
_values[i] = GetValueFromBits(bits, start + i * subLength, start + subLength + i * subLength - 1);
}

return _values;
}
GetValueFromBits是从所给的byte数组bits中截取从start位到end位的值,这个位的顺序是从左到右以0为起始索引逐一递增的.
截取的数值包括start位和end位,这点需要留意.
GetValuesFromBits是从所给的byte数组bits中从start位开始以subLength为每个值的长度截取valueCount个的值.位的顺序也是从左到右以0为起始索引逐一递增的.
新的Load方法就可以改成:
public void Load(BinaryReader binReader)

{
Nbits = (byte)(binReader.PeekChar() >> 3);

int _bitCount = Nbits * 4 + 5;
int _byteCount = _bitCount / 8;

if (_bitCount % 8 != 0) _byteCount++;

byte[] _rectInfo = binReader.ReadBytes(_byteCount);

string _bitStr = Common.Math.BytesToBin(_rectInfo);

Xmin = (int)BinMatch.GetValueFromBits(_rectInfo, 5, Nbits);
Xmax = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits + 5, Nbits), 2);
Ymin = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits * 2 + 5, Nbits), 2);
Ymax = (int)BinMatch.GetValueFromBits(_rectInfo,Nbits * 3 + 5, Nbits), 2);
}
一下给出我所写的用于对二进制数操作的一个类代码,里面其实就是一组我常用到的二进制取值之类的函数.
注:不好意思最近没什么时间,所以就没有写注释了,大家凑合着看吧,有问题或者建议可以留言告诉我

查看详细代码
public class BinMath

{
public static string BytesToBin(params byte[] bytes)

{
StringBuilder _result = new StringBuilder();

foreach (byte data in bytes)

{
byte _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0,_doc % 2);
_doc = (byte)(_doc / 2);
}

_result.Append(_newStr.ToString().PadLeft(8, '0'));
}
else

{
_result.Append("00000000");
}
}

return _result.ToString();
}

public static string UInt16sToBin(params UInt16[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (UInt16 data in integers)

{
UInt16 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = (UInt16)(_doc / 2);
}

_result.Append(_newStr.ToString().PadLeft(16, '0'));
}
else

{
_result.Append("0000000000000000");
}
}

return _result.ToString();
}
public static string Int16sToBin(params Int16[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (Int16 data in integers)

{
Int16 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = (Int16)(_doc / 2);
}

_result.Append(_newStr.ToString().PadLeft(16, '0'));
}
else

{
_result.Append("0000000000000000");
}
}

return _result.ToString();
}

public static string UInt32sToBin(params UInt32[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (UInt32 data in integers)

{
UInt32 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = _doc / 2;
}

_result.Append(_newStr.ToString().PadLeft(32, '0'));
}
else

{
_result.Append("00000000000000000000000000000000");
}
}

return _result.ToString();
}

public static string Int32sToBin(params Int32[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (Int32 data in integers)

{
Int32 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = _doc / 2;
}

_result.Append(_newStr.ToString().PadLeft(32, '0'));
}
else

{
_result.Append("00000000000000000000000000000000");
}
}

return _result.ToString();
}

public static string UInt64sToBin(params UInt64[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (UInt64 data in integers)

{
UInt64 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = _doc / 2;
}

_result.Append(_newStr.ToString().PadLeft(64, '0'));
}
else

{
_result.Append("0000000000000000000000000000000000000000000000000000000000000000");
}
}

return _result.ToString();
}

public static string Int64sToBin(params Int64[] integers)

{
StringBuilder _result = new StringBuilder();

foreach (Int64 data in integers)

{
Int64 _doc = data;

if (_doc != 0)

{
StringBuilder _newStr = new StringBuilder();

while (_doc != 0)

{
_newStr.Insert(0, _doc % 2);
_doc = _doc / 2;
}

_result.Append(_newStr.ToString().PadLeft(64, '0'));
}
else

{
_result.Append("0000000000000000000000000000000000000000000000000000000000000000");
}
}

return _result.ToString();
}

public static Byte GetByteBits(Byte source, byte start, byte end)

{
if (start >= 0 && start <= 7 && end >= 0 && end <= 7)

{
Byte _toolNum = 0xFF;

_toolNum = (Byte)(_toolNum >> start);
_toolNum = (Byte)(_toolNum << start);
_toolNum = (Byte)(_toolNum << (7 - end));
_toolNum = (Byte)(_toolNum >> (7 - end));

return (Byte)((source & _toolNum) >> start);
}

return 0;
}

public static UInt16 GetUInt16Bits(UInt16 source, byte start, byte end)

{
if (start >= 0 && start <= 15 && end >= 0 && end <= 15)

{
UInt16 _toolNum = 0xFFFF;

_toolNum = (UInt16)(_toolNum >> start);
_toolNum = (UInt16)(_toolNum << start);
_toolNum = (UInt16)(_toolNum << (15 - end));
_toolNum = (UInt16)(_toolNum >> (15 - end));

return (UInt16)((source & _toolNum) >> start);
}

return 0;
}

public static UInt32 GetUInt32Bits(UInt32 source, byte start, byte end)

{
if (start >= 0 && start <= 31 && end >= 0 && end <= 31)

{
UInt32 _toolNum = 0xFFFFFFFF;

_toolNum = _toolNum >> start;
_toolNum = _toolNum << start;
_toolNum = _toolNum << (31 - end);
_toolNum = _toolNum >> (31 - end);

return (source & _toolNum) >> start;
}

return 0;
}

public static UInt64 GetUInt64Bits(UInt64 source, byte start, byte end)

{
if (start >= 0 && start <= 63 && end >= 0 && end <= 63)

{
UInt64 _toolNum = 0xFFFFFFFFFFFFFFFF;

_toolNum = _toolNum >> start;
_toolNum = _toolNum << start;
_toolNum = _toolNum << (63 - end);
_toolNum = _toolNum >> (63 - end);

return (source & _toolNum) >> start;
}

return 0;
}

public static UInt64 GetValueFromBits(byte[] bits, int start, int end)

{
if (bits == null || bits.Length == 0
|| start < 0 || end > bits.Length - 1 || start > end)
return 0;

UInt64 _value = 0;
int _length = 0;
int _valueLength = end - start + 1;

int _byteIndex = 0;
byte _byteValue = 0;
UInt64 _tempValue = 0;

for (int i = 0; i < bits.Length; i++)

{
_byteIndex = i * 8;

if (_byteIndex > end) break;
if (_byteIndex + 7 < start) continue;

_byteValue = bits[i];

int _byteStart = start - _byteIndex;
int _byteEnd = end - _byteIndex;

if (_byteStart < 0) _byteStart = 0;

if (_byteEnd > 7) _byteEnd = 7;
else if (_byteEnd < 0) _byteEnd = _byteIndex - end;

_byteValue = (byte)(_byteValue << _byteStart);
_byteValue = (byte)(_byteValue >> _byteStart);
_byteValue = (byte)(_byteValue >> (7 - _byteEnd));

_length += _byteEnd - _byteStart + 1;

_tempValue = _byteValue;

_tempValue = _tempValue << (_valueLength - _length);

_value = _value | _tempValue;
}

return _value;
}

public static UInt64[] GetValuesFromBits(byte[] bits, int start, int subLength, int valueCount)

{
if (bits == null || bits.Length == 0
|| start < 0 || valueCount <= 0 || subLength <= 0
|| start + subLength * valueCount > bits.Length)
return null;

UInt64[] _values = new UInt64[valueCount];

for (int i = 0; i < valueCount; i++)

{
_values[i] = GetValueFromBits(bits, start + i * subLength, start + subLength + i * subLength - 1);
}

return _values;
}
}