读取位对齐数据

时间:2012-08-16 00:38:06

标签: c# .net-3.5

我一直在阅读Adobe网站上提供的SWF format,它提到为了节省空间,可变位用于存储整数或浮点数(pdf中的第17页)

我一直使用字节对齐的数据,因此没有考虑到位对齐的文件,或者在每个字节中存储信息的情况下进行可变对齐。

例如,您可能有一个包含四个顺序存储的13位整数的结构(而不​​是将它们存储为四个16位整数)。

前13位是第一个整数,接下来的13位是第二个整数,依此类推。它填充适当的最后一个字节,使结构与文件的其余部分字节对齐,因此52位将填充为56位,需要7个字节来存储这4个整数而不是8个字节。

  • 我如何处理这类问题?
  • 如何在位级处理字节流?
  • 我可以使用哪些东西来帮助您更轻松地处理这些数据吗?

我认为解决方案归结为在字节数组上使用位操作。

解析四个13位整数的示例解决方案也很好,以演示使用您建议的方法。

2 个答案:

答案 0 :(得分:3)

我知道有两种方法可以解决这个问题。第一种是手动执行 - 在字节数组上使用逐位运算符,除法,模数等[或者如果你感到无聊则使用整数/ ulong等]。 IsBitSet Example

另一种方式是BitArray - 它可以为你处理大部分内容:)


  

最好添加一个BitArray如何处理将13..25位作为int的示例,因为这将是主要操作。乍一看,我只看到一个循环。

很好......我写了一个快速&脏测试概念证明:

var rnd = new Random();
//var data = Enumerable.Range(0, 10).ToArray();
var data = Enumerable.Range(0, 10).Select(x => rnd.Next(1 << 13)).ToArray();

foreach (var n in data) Console.WriteLine(n);

Console.WriteLine(new string('-', 13));

var bits = new BitArray(data.Length * 13);

for (int i = 0; i < data.Length; i++)
{
    var intBits = new BitArray(new[] { data[i] });
    for (int b = 12; b > -1; b--)
    {
        bits[i * 13 + b] = intBits[b];
        Console.Write(intBits[b] ? 1 : 0);
    }
    Console.WriteLine();
}
Console.WriteLine(new string('-', 13));

for (int i = 0; i < bits.Length / 13; i++)
{
    int number = 0;
    for (int b = 12; b > -1; b--)
        if (bits[i * 13 + b])
            number += 1 << b;

    Console.WriteLine(number);
}
Console.ReadLine();

哪个输出:

910
3934
7326
7990
7712
1178
6380
3460
5113
7489
-------------
0001110001110
0111101011110
1110010011110
1111100110110
1111000100000
0010010011010
1100011101100
0110110000100
1001111111001
1110101000001
-------------
910
3934
7326
7990
7712
1178
6380
3460
5113
7489

除了简化访问之外,位阵列没有做太多 - 它仍然非常手动。我希望你能编写自己的类来简化它并使其整洁可重用 - 例如,这是另一个快速概念:

//Improved to take sign into account.
//Sign is in addition to bits allocated for storage in this version.
//Stored as {sign}{bits}
//E.g.  -5, stored in 3 bits signed is:
//       1 101
//E.g.   5, stored in 3 bits [with sign turned on]
//       0 101
//E.g.   5, stored in 3 bits no sign
//         101  
//This may differ from your exiting format - e.g. you may use two's compliments.
static void Main(string[] args)
{
    int bitsPerInt = 13;

    //Create your data
    var rnd = new Random();
    //var data = Enumerable.Range(-5, 10).ToArray();
    var data = Enumerable.Range(0, 10).Select(x => rnd.Next(-(1 << bitsPerInt), 1 << bitsPerInt)).ToArray();

    var bits = new BitSerlializer();

    //Add length header
    bits.AddInt(data.Length, 8, false);
    foreach (var n in data)
    {
        bits.AddInt(n, bitsPerInt);
        Console.WriteLine(n);
    }

    //Serialize to bytes for network transfer etc.
    var bytes = bits.ToBytes();

    Console.WriteLine(new string('-', 10));
    foreach (var b in bytes) Console.WriteLine(Convert.ToString(b, 2).PadLeft(8, '0'));
    Console.WriteLine(new string('-', 10));

    //Deserialize
    bits = new BitSerlializer(bytes);
    //Get Length Header
    var count = bits.ReadInt(8, false);
    for (int i = 0; i < count; i++)
        Console.WriteLine(bits.ReadInt(bitsPerInt));

    Console.ReadLine();
}

public class BitSerlializer
{
    List<byte> bytes;
    int Position { get; set; }

    public BitSerlializer(byte[] initialData = null)
    {
        if (initialData == null)
            bytes = new List<byte>();
        else
            bytes = new List<byte>(initialData);
    }

    public byte[] ToBytes() { return bytes.ToArray(); }

    public void Addbit(bool val)
    {
        if (Position % 8 == 0) bytes.Add(0);
        if (val) bytes[Position / 8] += (byte)(128 >> (Position % 8));
        Position++;
    }

    public void AddInt(int i, int length, bool isSigned = true)
    {
        if (isSigned) Addbit(i < 0);
        if (i < 0) i = -i;

        for (int pos = --length; pos >= 0; pos--)
        {
            var val = (i & (1 << pos)) != 0;
            Addbit(val);
        }
    }

    public bool ReadBit()
    {
        var val = (bytes[Position / 8] & (128 >> (Position % 8))) != 0;
        ++Position;
        return val;
    }

    public int ReadInt(int length, bool isSigned = true)
    {
        var val = 0;
        var sign = isSigned && ReadBit() ? -1 : 1;

        for (int pos = --length; pos >= 0; pos--)
            if (ReadBit())
                val += 1 << pos;

        return val * sign;
    }
}

答案 1 :(得分:3)

另一方面,基于字节数组的方法可能是这样的:

    int extend(uint raw, int bits)
    {
        int sh = 32 - bits;
        int x = (int)raw << sh; // puts your sign bit in the highest bit.
        return x >> sh;  // since x is signed this is an arithmatic signed shift
    }

    int read(byte[] data, int pos, int bits, bool signed)
    {
        int fbi = pos / 8; // first byte index
        int lbi = (pos + bits - 1) / 8; // last byte index
        int cnt = lbi - fbi + 1; // bytes spanned
        if (cnt > 3 || lbi >= data.Length) { throw new ArgumentException(); }

        uint raw = (uint)(
            (data[fbi] << (24 + pos % 8)) + 
            (cnt < 2 ? 0 : data[fbi + 1] << (16 + pos % 8)) + 
            (cnt < 3 ? 0 : data[fbi + 2] << (8 + pos % 8))
            ) >> (32 - bits);
        return signed ? extend(raw, bits) : (int)raw;
    }

测试:

    byte[] test = { 0x55, 0xAA, 0x10 };

    string s = "";
    s += read(test, 0, 8, false) + "\r\n";
    s += read(test, 0, 8, true) + "\r\n";
    s += read(test, 8, 8, false) + "\r\n";
    s += read(test, 8, 8, true) + "\r\n";
    s += read(test, 4, 8, false) + "\r\n";
    s += read(test, 7, 9, true) + "\r\n";
    s += read(test, 7, 10, true) + "\r\n";
    s += read(test, 7, 11, true) + "\r\n";
    s += read(test, 7, 12, true) + "\r\n";
    s += read(test, 7, 13, true) + "\r\n";
    s += read(test, 7, 14, true) + "\r\n";
    s += read(test, 7, 15, true) + "\r\n";
    s += read(test, 7, 16, true) + "\r\n";
    s += read(test, 7, 17, true) + "\r\n";
    s += read(test, 18, 2, true) + "\r\n";
    s += read(test, 18, 3, true) + "\r\n";
    s += read(test, 23, 1, true) + "\r\n";
    s += read(test, 23, 2, true) + "\r\n";

测试构建如下字符串:

    85
    85
    170
    -86
    90
    -86
    -172
    -344
    -688
    -1375
    -2750
    -5500
    -11000
    -22000
    1
    2
    0

然后在最后一行抛出异常。