我同意 keraba 的观点,即您需要使用 Huffman 编码或 Lempel-Ziv-Welch 算法之类的东西。您所说的位打包方式的问题是您有两个选择:
- 选择一个常数 n,以便可以表示最大的整数。
- 允许 n 因值而异。
第一个选项相对容易实现,但除非所有整数都相当小,否则真的会浪费很多空间。
第二个选项的主要缺点是您必须以某种方式在输出比特流中传达 n 的变化。例如,每个值都必须有一个与之关联的长度。这意味着您要为每个输入值存储两个整数(尽管整数较小)。您很有可能会使用此方法增加文件大小。
Huffman 或 LZW 的优势在于,它们以这样一种方式创建码本,即可以从输出比特流中导出代码的长度,而无需实际存储长度。这些技术使您可以非常接近香农极限。
我决定尝试一下您的原始想法(常量 n,删除未使用的位并打包),这是我想出的天真的实现:
#include <sys/types.h>
#include <stdio.h>
int pack(int64_t* input, int nin, void* output, int n)
{
int64_t inmask = 0;
unsigned char* pout = (unsigned char*)output;
int obit = 0;
int nout = 0;
*pout = 0;
for(int i=0; i<nin; i++)
{
inmask = (int64_t)1 << (n-1);
for(int k=0; k<n; k++)
{
if(obit>7)
{
obit = 0;
pout++;
*pout = 0;
}
*pout |= (((input[i] & inmask) >> (n-k-1)) << (7-obit));
inmask >>= 1;
obit++;
nout++;
}
}
return nout;
}
int unpack(void* input, int nbitsin, int64_t* output, int n)
{
unsigned char* pin = (unsigned char*)input;
int64_t* pout = output;
int nbits = nbitsin;
unsigned char inmask = 0x80;
int inbit = 0;
int nout = 0;
while(nbits > 0)
{
*pout = 0;
for(int i=0; i<n; i++)
{
if(inbit > 7)
{
pin++;
inbit = 0;
}
*pout |= ((int64_t)((*pin & (inmask >> inbit)) >> (7-inbit))) << (n-i-1);
inbit++;
}
pout++;
nbits -= n;
nout++;
}
return nout;
}
int main()
{
int64_t input[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int64_t output[21];
unsigned char compressed[21*8];
int n = 5;
int nbits = pack(input, 21, compressed, n);
int nout = unpack(compressed, nbits, output, n);
for(int i=0; i<=20; i++)
printf("input: %lld output: %lld\n", input[i], output[i]);
}
这是非常低效的,因为一次只执行一步,但这是在不处理字节顺序问题的情况下实现它的最简单方法。我也没有使用广泛的值对此进行测试,只是测试中的值。此外,没有边界检查,并且假定输出缓冲区足够长。所以我要说的是,这段代码可能只用于教育目的,让你开始。