将整数数组转换为字节数组的快速方法(11位)

时间:2017-12-15 00:10:24

标签: c# arrays

我有整数数组,我需要将其转换为字节数组
但是我需要(仅且仅仅)取整数数组的每个元素的前11位 然后将其转换为字节数组
我试过这段代码

// ***********convert integer values to byte values
//***********to avoid the left zero padding on the byte array
// *********** first step : convert to binary string 
 // ***********second step : convert binary string to byte array
 // *********** first step
string ByteString = Convert.ToString(IntArray[0], 2).PadLeft(11,'0');
for (int i = 1; i < IntArray.Length; i++)
            ByteString = ByteString + Convert.ToString(IntArray[i], 2).PadLeft(11, '0');



        // ***********second step

        int numOfBytes = ByteString.Length / 8;
        byte[] bytes = new byte[numOfBytes];
        for (int i = 0; i < numOfBytes; ++i)
        {
            bytes[i] = Convert.ToByte(ByteString.Substring(8 * i, 8), 2);

        }

但是它需要很长时间(如果文件大小很大,代码需要超过1分钟)

我需要一个非常快速的代码(仅限几毫秒)
任何人都可以帮助我吗?

2 个答案:

答案 0 :(得分:0)

如果要将int的最低有效11位存储到两个字节中,使得最低有效字节的位为1-8(含),最高有效字节为9-11:

int toStore = 123456789;
byte msb = (byte) ((toStore >> 8) & 7); //or 0b111
byte lsb = (byte) (toStore & 255); //or 0b11111111

要检查这一点,二进制的123456789是:

0b111010110111100110100010101
                  MMMLLLLLLLL

L以上的位是lsb,其值为21,M以上是msb且值为5

完成工作是移位运算符&gt;&gt;所有二进制数字都滑到右边的8个位置(其中8个从右侧消失 - 它们已经消失,被遗忘):

0b111010110111100110100010101 >> 8 =
        0b1110101101111001101 

面具操作员&amp; (掩码运算符只保留位,在每个位置,它们在值中为1,在掩码中为1):

0b111010110111100110100010101 &
0b000000000000000000011111111 (255) =
0b000000000000000000000010101 

如果您正在处理一个int数组,只需在循环中执行此操作:

byte[] bs = new byte[ intarray.Length*2 ];
for(int x = 0, b=0; x < intarray.Length; x++){
  int toStore = intarray[x];
  bs[b++] = (byte) ((toStore >> 8) & 7);
  bs[b++] = (byte) (toStore & 255);
}

答案 1 :(得分:0)

基本上,你会做很多转移和掩饰。具体的性质取决于您想要的布局。如果我们假设我们从每个int中包含little-endian,在左边附加 ,那么两个带位置的11位整数:

abcdefghijk lmnopqrstuv

成为8位块:

defghijk  rstuvabc 00lmnopq

(即取第一个整数的最低8位,剩下3个,所以将它们打包到下一个字节的低3位,然后取第二个整数的最低5位,最后取剩下的6位)比特,用零填充),然后像这样的东西应该工作:

using System;
using System.Linq;

static class Program
{
    static string AsBinary(int val) => Convert.ToString(val, 2).PadLeft(11, '0');
    static string AsBinary(byte val) => Convert.ToString(val, 2).PadLeft(8, '0');
    static void Main()
    {
        int[] source = new int[1432];
        var rand = new Random(123456);
        for (int i = 0; i < source.Length; i++)
            source[i] = rand.Next(0, 2047); // 11 bits

        // Console.WriteLine(string.Join(" ", source.Take(5).Select(AsBinary)));


        var raw = Encode(source);
        // Console.WriteLine(string.Join(" ", raw.Take(6).Select(AsBinary)));
        var clone = Decode(raw);

        // now prove that it worked OK
        if (source.Length != clone.Length)
        {
            Console.WriteLine($"Length: {source.Length} vs {clone.Length}");
        }
        else
        {
            int failCount = 0;
            for (int i = 0; i < source.Length; i++)
            {
                if (source[i] != clone[i] && failCount++ == 0)
                {
                    Console.WriteLine($"{i}: {source[i]} vs {clone[i]}");
                }
            }
            Console.WriteLine($"Errors: {failCount}");
        }
    }
    static byte[] Encode(int[] source)
    {
        long bits = source.Length * 11;
        int len = (int)(bits / 8);
        if ((bits % 8) != 0) len++;

        byte[] arr = new byte[len];
        int bitOffset = 0, index = 0;
        for (int i = 0; i < source.Length; i++)
        {
            // note: this encodes little-endian
            int val = source[i] & 2047;
            int bitsLeft = 11;
            if(bitOffset != 0)
            {
                val = val << bitOffset;
                arr[index++] |= (byte)val;
                bitsLeft -= (8 - bitOffset);
                val >>= 8;
            }

            if(bitsLeft >= 8)
            {
                arr[index++] = (byte)val;
                bitsLeft -= 8;
                val >>= 8;
            }
            if(bitsLeft != 0)
            {
                arr[index] = (byte)val;
            }
            bitOffset = bitsLeft;
        }
        return arr;
    }

    private static int[] Decode(byte[] source)
    {
        int bits = source.Length * 8;
        int len = (int)(bits / 11);
        // note no need to worry about remaining chunks - no ambiguity since 11 > 8

        int[] arr = new int[len];
        int bitOffset = 0, index = 0;
        for(int i = 0; i < source.Length; i++)
        {
            int val = source[i] << bitOffset;
            int bitsLeftInVal = 11 - bitOffset;
            if(bitsLeftInVal > 8)
            {
                arr[index] |= val;
                bitOffset += 8;
            }
            else if(bitsLeftInVal == 8)
            {
                arr[index++] |= val;
                bitOffset = 0;
            }
            else
            {
                arr[index++] |= (val & 2047);
                if(index != arr.Length) arr[index] = val >> 11;
                bitOffset = 8 - bitsLeftInVal;
            }
        }
        return arr;

    }
}

如果您需要不同的布局,您需要调整它。

这在我的机器上只需一秒钟即可编码512 MiB。

Encode方法概述:

首先要做的是预先计算所需的空间量,并分配输出缓冲区;因为每个输入对输出贡献11位,所以这只是一些模数学运算:

    long bits = source.Length * 11;
    int len = (int)(bits / 8);
    if ((bits % 8) != 0) len++;

    byte[] arr = new byte[len];

我们知道输出位置不会与输入匹配,我们知道我们每次都会以不同的位置以字节为单位启动每个11位块,因此为这些位置分配变量,并循环通过输入:

    int bitOffset = 0, index = 0;
    for (int i = 0; i < source.Length; i++)
    {
        ...
    }
    return arr;

所以:依次取每个输入(其中输入是位置i的值),取值的低11位 - 并观察我们有11位(这个值)仍然要写:

        int val = source[i] & 2047;
        int bitsLeft = 11;

现在,如果当前输出值是部分写入(即bitOffset != 0),我们应该首先处理。当前输出中剩余的空间量为8 - bitOffset。由于我们总是有11个输入位,所以我们不需要担心空间比填充值要多,所以:左移我们的值bitOffset(右侧的焊盘为bitOffset个零,作为二元运算),&#34;或&#34;输出字节的最低8位。基本上这表示&#34;如果bitOffset为3,则将val的5个低位写入输出缓冲区的5个高位&#34 ;;最后,修正这些值:增加我们的写入位置,记录我们仍然要写入的当前值的较少位,并使用右移来丢弃val的8个低位(由{{1}组成零和bitOffset&#34;真实&#34;位):

8 - bitOffset

接下来的问题是:我们是否(至少)留下了整个数据字节?例如,如果 if(bitOffset != 0) { val = val << bitOffset; arr[index++] |= (byte)val; bitsLeft -= (8 - bitOffset); val >>= 8; } 为1,我们可能不会这样做(所以我们已经写了7位,只留下4位)。如果我们,我们可以将其标记为向下并增加写入位置 - 然后再次跟踪剩余的数量并丢弃低8位:

bitOffset

可能我们还有一些遗留问题;例如,如果 if(bitsLeft >= 8) { arr[index++] = (byte)val; bitsLeft -= 8; val >>= 8; } 为7,我们将在第一个块中写入1位,在第二个块中写入8位,再写2个 - 或者如果bitOffset为0则我们赢了&#39 ; t在第一个块中写入任何内容,在第二个块中写入8,剩下3个写入。因此,记下剩余的内容,但增加写入位置 - 我们已写入低位,但下一个值可能需要写入高位。最后,将bitOffset更新为,但是我们在最后一步中写了很多低位(可能为零):

bitOffset

if(bitsLeft != 0) { arr[index] = (byte)val; } bitOffset = bitsLeft; 操作与此逻辑相反 - 再次,计算尺寸并准备状态:

Decode

现在循环输入:

    int bits = source.Length * 8;
    int len = (int)(bits / 11);

    int[] arr = new int[len];
    int bitOffset = 0, index = 0;

现在, for(int i = 0; i < source.Length; i++) { ... } return arr; 是我们想要在当前11位值中写入的起始位置,所以如果我们从头开始,它将在第一个时为0字节,然后8;第二个字节的3位与第一个11位整数连接,因此5位成为第二个字节的一部分 - 所以bitOffset在第3个字节上是5,等等。我们可以计算剩余的位数在当前整数中减去11:

bitOffset

现在我们有3种可能的情况:

1)如果我们更多比当前值剩下的8位,我们可以记下我们的输入(按位&#34;或&#34;)但增加写入位置(因为我们要为此值写更多内容),并注意我们进一步沿着8位:

        int val = source[i] << bitOffset;
        int bitsLeftInVal = 11 - bitOffset;

2)如果我们在当前值中剩下完全 8位,我们可以记下我们的输入(按位&#34;或&#34;)增加写入位置;下一个循环可以从零开始:

        if(bitsLeftInVal > 8)
        {
            arr[index] |= val;
            bitOffset += 8;
        }

3)否则,我们在当前值中剩下小于 8位;所以我们需要将第一个 else if(bitsLeftInVal == 8) { arr[index++] |= val; bitOffset = 0; } 位写入当前输出位置(递增输出位置),以及留在 next 输出位置的任何内容。由于我们已经向左移bitsLeftInVal,这实际上意味着简单:将低11位(bitOffset)压低到当前位置(按位&#34;或&#34;) ,以及剩下的任何内容(val & 2047)到下一个 if ,它不会超过我们的输出缓冲区(填充零)。然后计算新的val >> 11

bitOffset

基本上就是这样。大量按位操作 - 转换( else { arr[index++] |= (val & 2047); if(index != arr.Length) arr[index] = val >> 11; bitOffset = 8 - bitsLeftInVal; } / <<),屏蔽(>>)和组合(&)。