大小有效的方式传输数组的数字作为文本

本文关键字:数字 文本 数组 传输 有效 方式 | 更新日期: 2023-09-27 17:50:32

假设我有一个数字数组:
1,5,500,994,6950,54,54,845,101,54046506452,5980,960406,55,680,68045,66540,321032

用http post将其传输到一个将其解码回数字的网站的最有效的方法是什么?

如果我将其传输为文本"1,5,500,994,6950,54,54,845,101,54046506452,5980,960406,55,680,68045,66540,321032",则每1位数字和分隔符占用1字节,这是浪费数据,因为我可以使用以下所有字符:a-z, A-Z, 0-9, -, ., _, ~允许在http中没有百分比编码。

我可以将十进制数字系统重新计算为65数字系统,该系统使用abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._并使用~作为分隔符,使其看起来像b~f~hT~pt~bP8~2~2~na~bK~ULUhIQ~bBa~dGuF~3~kE~qg3~pWT~bk.-。但在我的程序中,我主要使用长度为3-5个字符的数字。因此,大约1/5的数据只是分隔符。难道没有更好的解决办法吗?

这个问题是故意问的,因为我对任何创造性的解决方案都持开放态度。

大小有效的方式传输数组的数字作为文本

我会尝试将您的12个可能的字符转换为4位数字,如

byte[] fourBitArr = new byte[input.Length];
for (int i = 0; i < input.Length; i++) {
    switch (input[i])
    {
        case '0': fourBitArr[i] = 0x0; break;
        case '1': fourBitArr[i] = 0x1; break;
        case '2': fourBitArr[i] = 0x2; break;
        case '3': fourBitArr[i] = 0x3; break;
        case '4': fourBitArr[i] = 0x4; break;
        case '5': fourBitArr[i] = 0x5; break;
        case '6': fourBitArr[i] = 0x6; break;
        case '7': fourBitArr[i] = 0x7; break;
        case '8': fourBitArr[i] = 0x8; break;
        case '9': fourBitArr[i] = 0x9; break;
        case ',': fourBitArr[i] = 0xA; break;
        case '-': fourBitArr[i] = 0xB; break;
    }
}

然后我将创建一个byte[],并在每个字节中放入2个字符(通过将4位移到前面):

byte[] byteArr = new byte[(input.Length+1)/2];
for (int i = 0; i < byteArr.Length; i++)
{
    byteArr[i] = (byte)fourBitArr[2*i];
    if (fourBitArr.Length > 2 * i + 1)
        byteArr[i] += (byte)(fourBitArr[2 * i + 1] << 4);
    else
        byteArr[i] += (0xF << 4);
}

生成的byte[]可以用Base64编码。这应该用1,33个字符编码每个字节(前两个字符)。

string output = Convert.ToBase64String(byteArr);

注意Base64创建了一个包含+, /=的字符串,它们在url中有特殊的含义。您可以用以下允许的字符-._~

替换它们中的每一个

上面的算法缩短了示例输入

1,5,500,994,6950,54,54,845,101,54046506452,5980,960406,55,680,68045,66540,321032
oaUFoJmklgVapEWKVBoQWgRkBUYlWomgaUBgWqWGoIZApWZFoCMBIw==

这不是最优的,但离最优值也不远,如果你考虑到输入的熵是每个字符约3.5位,而URL编码字符的熵约为6,05位。通过找到一种方法来利用0xC、0xD、0xE、0xF作为fourByteArr的可能值,可以对算法进行优化。

奖金:

将其还原为原始字符串的代码:

byte[] byteArrReverse = Convert.FromBase64String(output);
StringBuilder sb = new StringBuilder();
foreach (byte b in byteArrReverse)
{
    for (int i = 0; i < 2; i++)
    {
        byte fourBit = (byte)((i == 0) ? b & 0xF : b >> 4);
        switch (fourBit)
        {
            case 0x0: sb.Append('0'); break;
            case 0x1: sb.Append('1'); break;
            case 0x2: sb.Append('2'); break;
            case 0x3: sb.Append('3'); break;
            case 0x4: sb.Append('4'); break;
            case 0x5: sb.Append('5'); break;
            case 0x6: sb.Append('6'); break;
            case 0x7: sb.Append('7'); break;
            case 0x8: sb.Append('8'); break;
            case 0x9: sb.Append('9'); break;
            case 0xA: sb.Append(','); break;
            case 0xB: sb.Append('-'); break;
        }
    }
}
string inputReverse = sb.ToString();

这是一个基于h3n使用1字节2个字符的想法的正长整数压缩和解压缩的完整代码,谢谢!它使用15个数字系统:从0000到1110二进制和1111 (0xF)作为分隔符。

压缩:

public List<byte> EncodeNumbers(List<long> input)
{
    List<byte> bytes = new List<byte>();
    int bytes_i = 0;
    for (int a = 0; a < input.Count; a++)
    {
        int buffer_i = 65;
        byte[] buffer = new byte[buffer_i];
        while (input[a] > 0)
        {
            buffer[--buffer_i] = (byte)(input[a] %15);
            input[a] /= 15;
        }
        for (int b = 0; b < 65 -buffer_i; b++)
        {
            if (bytes_i %2 == 0)
            {
                bytes.Add((byte)(buffer[b +buffer_i] << 4));
                bytes_i++;
            }else{
                bytes[bytes_i++ /2] += buffer[b +buffer_i];
            }
        }
        if (a +1 != input.Count)
        {
            if (bytes_i %2 == 0)
            {
                bytes.Add(0xF << 4);
                bytes_i++;
            }else{
                bytes[bytes_i++ /2] += 0xF;
            }
        }
        else if (bytes_i %2 != 0)
        {
            bytes[bytes_i++ /2] += 0xF;
        }
    }
    return bytes;
}

减压:

public List<long> DecodeNumbers(List<byte> input)
{
    List<long> numbers = new List<long>();
    int buffer_i = 0;
    byte[] buffer = new byte[17]; // max long = 9223372036854775807 = 160E2AD3246366807 (17 chars)
    for (int a = 0; a < input.Count; a++)
    {
        for (int i = 0; i < 2; i++)
        {
            byte value = (byte)((i == 0) ? input[a] >> 4 : input[a] & 0xF);
            if (value != 0xF)
            {
                buffer[buffer_i++] = value;
            }else{
                long number = 0;
                for (int b = 0; b < buffer_i; b++)
                {
                    number += buffer[buffer_i -1 -b] *(long)Math.Pow(15, b);
                }
                buffer_i = 0;
                numbers.Add(number);
            }
        }
    }
    if (buffer_i != 0)
    {
        long number = 0;
        for (int b = 0; b < buffer_i; b++)
        {
            number += buffer[buffer_i -1 -b] *(long)Math.Pow(15, b);
        }
        numbers.Add(number);
    }
    return numbers;
}

用法:

List<long> numbers = new List<long>{4,10,14,51,5990,922337203685477,64,4685746,56545674,94,1,65454677,665555,1234567890,55555,22,2,3,2,0,99999,99955500099955577,1,2,666,654154,654,58,56,69,7,55,5647,321,25,0,697,9,9,9,9,9,96,5,546,4,645545,64564564,5465498654,6476854,85849865,6478596743,6,6,1,2,3,3,3,548745,6647};
string s = "plain text:'r'n";
string str = "";
foreach (long val in numbers)
{
    str += val + "|";
}
s += str + "'r'n" + str.Length + " bytes'r'n'r'n";
List<byte> encoded = EncodeNumbers(numbers);
s += "compressed base64:'r'n";
str = Convert.ToBase64String(encoded.ToArray());
s += str + "'r'n" + str.Length + " bytes'r'n'r'n";
List<long> decompressed = DecodeNumbers(encoded);
str = "";
foreach (long val in decompressed)
{
    str += val + "|";
}
s += "decompressed:'r'n" + str + "'r'n" + str.Length + " bytes";
Clipboard.SetText(s);
输出:

plain text:
4|10|14|51|5990|922337203685477|64|4685746|56545674|94|1|65454677|665555|1234567890|55555|22|2|3|2|0|99999|99955500099955577|1|2|666|654154|654|58|56|69|7|55|5647|321|25|0|697|9|9|9|9|9|96|5|546|4|645545|64564564|5465498654|6476854|85849865|6478596743|6|6|1|2|3|3|3|548745|6647|
278 bytes
compressed base64:
T6/vNvG5X3GXGSLIRS9E9ihYH05uQZ9k8fWy3qL9IwX3NbfWDxFtrxfy8/L/HpafNlXR4iHg2i8fLy5vzcVPLZ89879J9/OvGhfxZvGv8xf5+fn5+fZvXyZvT8tBX1oFOU8h7FcB74fhBPeAvuXyfbdDSPb28fLz8/P6yNDx6C8=
172 bytes
decompressed:
4|10|14|51|5990|922337203685477|64|4685746|56545674|94|1|65454677|665555|1234567890|55555|22|2|3|2|0|99999|99955500099955574|1|2|666|654154|654|58|56|69|7|55|5647|321|25|0|697|9|9|9|9|9|96|5|546|4|645545|64564564|5465498654|6476854|85849865|6478596743|6|6|1|2|3|3|3|548745|6647|
278 bytes

由于数据类型转换,当数字接近long int的最大值时,会有一点数据丢失,可以在99955500099955577和99955500099955574上看到。

你可以用2字节每3个数字,包括符号,点和分隔符信息。

在ASCII字符串中,我们可以使用每个字节的7个最低有效位,注意每个字节都需要保存32或更高的值。所以对于一个2字节的集合我们可以命名为:

abcdefgh ijklmnop

位a和I总是零。比特h和p将用于表示分离。bde是第一个数字FGJK将是第二个数字Lmno将是第三位数字

要确保每个字节都承载一个32或更高的值,我们可以使用这种编码

数字1:

9 = 1111
8 = 1110
7 = 1101
6 = 1100
5 = 1011
4 = 1010
3 = 1001
2 = 1000
1 = 0111
0 = 0110
- = 0101
. = 0100

第二位:

. = 0010
- = 1010
0 = 0110
1 = 1110
2 = 0001
3 = 1001
4 = 0101
5 = 1101
6 = 0011
7 = 1011
8 = 0111
9 = 1111

第三位:前两种形式中的任何一种,例如作为第一个数字。当使用2位数块时,读取第三位的所有位为零表示第三位不存在。

位h和p信号,如果你需要读取额外的字节来构建这个数字。

对于单个数字:

0bcde000

对于2位数字:

0bcdefg1 0jk00000

对于3位数字:

0bcdefg1 0jklmno0

对于四位数字:

0bcdefg1 0jklmno1 0rstu000

等等…

不使用字节来分隔每个数字,您可以包括符号和小数分隔符(如点)。

如果大多数数据为3-5个字符,则平均每个数据为2-4个字节。

这样,您的示例数据将如下:
"1,5,500,994,6950,54,54,845,101,54046506452,5980,960406,55,680,68045,66540,321032"

共48字节,共81个字符:

"8X[L⌂tgw0[ [ s6;N[-Qw1uY _}0ylSX_ clclW awS I/50"

00111000 (56)
01011000 (88)
01011011 01001100 (91 76)
01111111 01110100 (127 116)
01100111 01110111 00110000 (103 119 48)
01011011 00100000 (91 32)
01011011 00100000 (91 32)
01110011 00110110 (115 54)
00111011 01001110 (59 78)
01011011 00101101 01010001 01110111 00110001 01110101 01011001 00100000 (91 45 81 119 49 117 89 32)
01011111 01111101 00110000 (95 125 48)
01111001 01101100 (121 108)
01010011 01011000 (83 88)
01011111 00100000 (95 32)
01100011 01101100 (99 108)
01100011 01101100 01010111 00100000 (99 108 87 32)
01100001 01110111 01010011 01000000 (97 119 83 32)
01001001 00101111 00110101 00110000 (73 47 53 48)