【问题标题】:Integer data compression for transfer in C without external libraries用于在 C 中传输的整数数据压缩,无需外部库
【发布时间】:2021-09-21 09:14:22
【问题描述】:

我在这里搜索了一堆没有合适的解决方案。标题可能有点奇怪或不完全准确,但让我解释一下: 我的 IoT 设备每秒收集一堆数据,我可以将其表示为整数列表。这是一行传感器读取的示例(零并不总是 0 btw):

230982 0 4294753011 -9 4294198951 -1 4294225518 0 0 0 524789 0 934585 0 4 0 0 0 0

在触发时,我想将整个表(直到那时的所有行)发送到我的计算机。我可以将其字符串化并连接所有内容,但想知道是否有更有效的编码/压缩来减少字节数,无论是在存储在 RAM/闪存中还是为了减少传输量。理想情况下,这可以通过集成功能来实现,即没有外部压缩库。我对编码/压缩不是很擅长,希望你能给我一个提示。

【问题讨论】:

  • 你知道数据,所以你必须知道如何压缩它(例如你知道限制)。也许只是将它们作为二进制数发送就足够了(你应该知道以字节为单位的大小)
  • 最佳策略取决于数据类型。仅凭少量数据很难确定。一种简单的可能性:对于每个整数,使用 1 或 2 位来指定用于编码整数的位/字节数,例如1字节/4字节/8字节...
  • @TedLyngmo 一般在物联网设备中,计算能力非常有限,功耗必须保持非常低。但这篇文章并没有明确说明这些约束是否有效。
  • @Damien 是的,我仔细阅读并删除了我的评论。
  • @André 这取决于范围。如果将示例中的大无符号值转换为有符号值,则显示的所有数字都适合 21 位,包括符号位,但将它们存储为 24 位更方便。当然,如果您需要存储 -8388608 到 8388607 范围之外的数字,那么 24 位是不够的。

标签: c encoding compression


【解决方案1】:

Zlib/Zstd 库更适合进行通用压缩。如果我假设您不想使用任何第三方库,这里是一些简单压缩方法的手动编码版本,它可以节省输入字符串的一半字节。

基本思想很简单。您的字符串最多有 16 个不同的字符,可以映射到 4 位而不是典型的 8 位。请参阅下面的假设。您也可以尝试base16base64base128 编码,但这是最简单的。 假设:

  1. 首先将所有数字转换为十进制格式的字符串。
  2. 字符串不会包含除0,1,2,3,4,5,6,7,8,9,+,-,.spacecomma 之外的任何其他字符。

================================================ ==============================

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static inline char map(char c)
{
        switch(c) {
        case ' ' : return ('/' - '*');
        case '\0': return 0;
        default  : return c - '*';
        }
        return 0;
}

static inline char revmap(char c)
{
        switch(c) {
        case '\0'     : return 0;
        case '/' - '*': return ' ';
        default       : return c + '*';
        }
        return 0;
}

char *compress(const char *s, int len)
{
        int i, j;
        char *compr = malloc((len+1)/2 + 1);

        j = 0;
        for (i = 1; i < len; i += 2)
                compr[j++] = map(s[i-1]) << 4 | map(s[i]);

        if (i-1 < len)
                compr[j++] = map(s[i-1]) << 4;

        compr[j] = '\0';
        return compr;
}

char *decompress(const char *s, int len)
{
        int i, j;
        char *decompr = malloc(2*len + 1);

        for (i = j = 0; i < len; i++) {
                decompr[j++] = revmap((s[i] & 0xf0) >> 4);
                decompr[j++] = revmap(s[i] & 0xf);
        }
        decompr[j] = '\0';
        return decompr;
}

int main()
{
        const char *input = "230982 0 4294753011 -9 4294198951 -1 4294225518 0 0 0 524789 0 934585 0 4 0 0 0 0 ";
        int plen = strlen(input);
        printf("plain(len=%d): %s\n", plen, input);

        char *compr = compress(input, plen);
        int clen = strlen(compr);

        char *decompr = decompress(compr, clen);
        int dlen = strlen(decompr);
        printf("decompressed(len=%d): %s\n", dlen, decompr);

        free(compr);
        free(decompr);
}

【讨论】:

  • 太棒了!真的很好的答案。我不完全了解会发生什么,但我会慢慢检查代码以学习。刚刚运行它,尽管有两个错误(compr 和 decompr 从 malloc 分配一个 void*,所以它必须是“= (char*)malloc”)它运行良好。
  • @André 不强制转换 void * 在 C 中完全有效,但在 C++ 中则无效。也许您启用了一些导致错误的标志。无论如何,感谢您接受答案。
  • 可能,我使用了 C Playground,它抛出了错误,尽管通常它与 C 很相似,但也许它是一个奇怪的 C/C++ 混合体。
【解决方案2】:

最简单的解决方案是以二进制形式简单地转储数据。根据您的数据,它可能比字符串形式更小或更大,但您不必在设备上进行任何数据处理。

如果您的大部分数据都很小,您可以使用可变长度数据编码进行序列化。有几个,但 CBOR 相当简单。

如果您的数据变化很小,您可以只发送第一行作为绝对值,其余行作为前一行的增量。这将导致许多小数字,这通常在前面提到的编码系统中更有效。

我不会尝试在没有任何经验和外部库的情况下实现任何通用压缩算法,除非您绝对需要它。找到合适的算法来充分压缩您的数据并合理使用资源可能会很耗时。

【讨论】:

  • 这几乎就是我根据原始问题的 cmets 所想到的。我将每个传感器视为一列。这些值有时变化很小或随机变化,但我可以使用差异存储来减少字节数,如果没有任何变化,我可以使用 @thebusybee 建议的 RLE 来进一步减少它。在二进制形式中,它将使其最紧凑,几乎不需要板载计算。
猜你喜欢
  • 1970-01-01
  • 2016-02-04
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多