2013-06-21 101 views
1

这是我难以解决的作业的一部分。将结构压缩成二进制文件? [C]

我有一个简单的结构:

typedef struct Client { 
    char* lname; 
    unsigned int id; 
    unsigned int car_id; 
} Client; 

而且锻炼是:

  1. 创建一个名为作为公司名称的文本文件,然后分枝数以txt推广。 该文件包含所有客户的详细信息。

  2. 您在练习1中创建的文件将被压缩。因此,使用.cmpr扩展名创建二进制文件。

我真的没有一个想法如何落实2

我记得在那个教授说,我们必须使用“全部”的变量,用二元运算符的演讲(< <,>>,|,&,〜),但我不知道如何使用它。

我使用的是GCC和Eclipse下的Ubuntu。我正在使用C.

我很乐意获得帮助。谢谢!

+1

那么,对于压缩,我会使用libzip或至少libz ...你是什么意思“不得不使用所有变量”? – 2013-06-21 08:15:35

+0

@ H2CO3例如,如果我使用'int',但仅使用5位,则将下一个变量压缩到剩下的3个中 – Billie

+0

@userXXX什么是“下一个变量”?左3 ...但是什么? – 2013-06-21 08:18:01

回答

5

比方说,从步骤1中的文件看起来像:

​​

其中三个领域简单地印上三行。请注意,以上是该文件的文本/可读(即ASCII)表示。

望着这个文件的十六进制内容(adecimal)表示,我们得到(印有下面的每个字节的值的ASCII字符):

75 73 65 72 31 37 39 38 33 36 32 0a 32 33 32 34 0a 34 36 32 33 34 35 0a 
u s e r 1 7 9 8 3 6 2 nl 2 3 2 4 nl 4 6 2 3 4 5 nl         

这里nl是当然的换行符。你可以指望有24个字节。

在第2步中,您必须发明另一种可以节省尽可能多的位的格式。最简单的方法是分别压缩三个字段中的每一个。

与文本格式使用nl标记字段结尾的位置类似,您还需要一种方法来定义二进制字段开始和结束的位置。一种常用的方法是在二进制字段数据前加上一个长度。作为第一步,我们可以用一个长度替换nl的,并得到:

58 75 73 65 72 31 37 39 38 33 36 32 20 32 33 32 34 30 34 36 32 33 34 35 
-- u s e r 1 7 9 8 3 6 2 -- 2 3 2 4 -- 4 6 2 3 4 5         

现在我们只是需要一个整体字节的比特长度。请注意,58是77的十六进制表示(即11个字符* 8位),位长为 30`具有特殊含义。

下一步将压缩每个字段。这是棘手的地方。 lname字段由ASCII字符组成。在ASCII中,只需要使用8位中的7位; here's a nice table例如,二进制中的字母u01110101。我们可以安全地切掉最左​​边的位,这总是0。这产生了1110101。所有角色都可以做到这一点。所以你会得到11个7位值 - > 77位。

这些77位现在必须适合8位字节。以下是第一个4个字节user在二进制表示,切碎最左边的位关闭之前:

unsigned char byte = lname[0]; 
byte = byte << 1; 

01110101 01110011 01100101 01110010 

斩去在C位由字节(即unsigned char)向左移位用做

当你这样做的所有字符你:

1110101- 1110011- 1100101- 1110010- 

这里我用-以表示这些字节的比特现在可以填补;它们通过将所有位移到一个位置而变得可用。您现在使用下一个字节右侧的一个或多个位来填补这些空白。做这件事时这四个字节,你会得到:

11101011 11001111 00101111 0010---- 

所以现在有4位认为应该充满从角色1位,填补了这些空白差距等

是通过使用你提到的C中的二元运算符来完成。我们已经使用左移<<。要结合例如1110101-1110011-我们这样做:

unsigned char* name; // name MUST be unsigned to avoid problems with binary operators. 
<allocated memory for name and read it from text file> 

unsigned char bytes[10]; // 10 is just a random size that gives us enough space. 

name[0] = name[0] << 1; // We shift to the left in-place here, so `name` is overwritten. 
name[1] = name[1] << 1; // idem. 

bytes[0] = name[0] | (name[1] >> 7); 
bytes[1] = name[1] << 1; 

随着name[1] >> 7我们1110011- >> 7这给:00000001;最右边的位。使用按位或运算符|,然后我们将该位添加到1110101-,导致111010111

你必须在循环中做这样的事情,以获得正确字节中的所有位。

这个名称字段的新长度是11 * 7 = 77,所以我们失去了一个巨大的11位:-)请注意,对于字节长度,我们假设lname字段永远不会超过255/7 = 36个字符。

与上面的字节一样,您可以合并第二个长度与lname字段的最后一位。

要压缩数字,您首先在unsigned int中读取'(em)(fscanf(file, %d, ...))。在这个4字节无符号整数的左边将会有很多0 s。例如第一字段(示出在仅是为了便于阅读4位的组块):

0000 0000 0000 0000 0000 1001 0001 0100 

其具有在左侧20未使用的位。

你需要摆脱这些。做32减去零的数字在左边,你得到这个数字的位长。将此长度与bytes阵列的位相加,并将其与以前的字段合并。然后只将该数字的有效位添加到bytes。这将是:

1001 0001 0100 

在C,有“廉政”的位工作时(也是“短”,“长”,......任何变量/数大于1个字节大),您必须考虑字节顺序或endianness

当您对两个数字执行上述步骤两次时,就完成了。然后你可以写一个bytes数组写入文件。当然你必须保留你在bytes上面的步骤中写的地方;所以你知道字节数。请注意,在大多数情况下,最后一个字节中会有几位未填充数据。但是这并没有什么坏处,并且它简单地避免了文件以最少8位= 1字节的形式存储的事实。

读取二进制文件时,您会得到一个反向过程。您将阅读unsigned charbytes数组。然后您知道第一个字节(即bytes[0])包含名称字段的位长。然后通过移位和掩码来填写'lname'字节的字节。 etc ....

祝你好运!