假设步骤 1 中的文件如下所示:
user1798362
2324
462345
其中三个字段简单地打印在三行上。请注意,以上是该文件的文本/可读(即 ASCII)表示。
以十六进制(十进制)表示形式查看该文件的内容,我们得到(在每个字节值下方打印 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 步中,您必须发明另一种格式,以尽可能多地保存位。最简单的方法是分别压缩这三个字段中的每一个。
与文本格式使用 anl
标记字段结尾的位置类似,您还需要一种方法来定义二进制字段的开始和结束位置。一种常见的方法是在二进制字段数据前面加上一个长度。作为第一步,我们可以用nl
长度替换 's 并得到:
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 位),位长lname',
20 hex equals 4 * 8 = 32, and
30 is 6 * 8 = 48. This does not compress anything, as it's still 24 bytes in total. But we already got a binary format because
58 ,
20 and
30` 有特殊含义。
下一步是压缩每个字段。这就是棘手的地方。该lname
字段由ASCII字符组成。在 ASCII 中,仅需要/使用 8 位中的 7 位;这是一个很好的表例如u
二进制中的字母是01110101
. 我们可以安全地切掉最左边的位,它总是0
. 这产生1110101
. 对所有角色都可以这样做。所以你最终会得到 11 个 7 位值 -> 77 位。
这 77 位现在必须适合 8 位字节。这是二进制表示的前 4 个字节user
,在砍掉最左边的位之前:
01110101 01110011 01100101 01110010
unsigned char
通过将字节(即)向左移动来完成 C 中的切掉一点:
unsigned char byte = lname[0];
byte = byte << 1;
当您对所有字符执行此操作时,您会得到:
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
我们有which1110011- >> 7
给出00000001
:最右边的位。然后我们使用按位或运算符|
将此位“添加”到1110101-
,从而得到111010111
。
您必须在循环中执行这样的操作才能获取正确字节中的所有位。
此名称字段的新长度为 11 * 7 = 77,因此我们丢失了大量的 11 位 :-) 请注意,对于字节长度,我们假设该lname
字段的长度永远不会超过 255 / 7 = 36 个字符.
与上面的字节一样,您可以将第二个长度与lname
字段的最终位合并。
要压缩您首先使用 ( fscanf(file, %d, ...)
)读取的数字unsigned int
。0
在这个 4 字节的 unsigned int 中,左侧会有很多s。例如,第一个字段是(以 4 位的块显示仅是为了便于阅读):
0000 0000 0000 0000 0000 1001 0001 0100
左侧有 20 个未使用的位。
你需要摆脱这些。做 32 减去左边零的个数,你就得到这个数字的位长。bytes
通过将其位与前一个字段的位合并,将此长度添加到数组中。然后只将数字的有效位添加到bytes
. 这将是:
1001 0001 0100
在 C 中,当使用 'int' 的位(还有 'short'、'long'、...任何大于 1 字节的变量/数字)时,您必须考虑字节顺序或字节序。
当您对两个数字执行上述步骤两次时,您就完成了。然后你有一个bytes
可以写入文件的数组。当然,您必须保留bytes
在上述步骤中写入的位置;所以你知道字节数。请注意,在大多数情况下,最后一个字节中会有一些未填充数据的位。但这并没有什么坏处,它只是不可避免地浪费了文件存储在 8 位 = 1 个字节的块中这一事实。
读取二进制文件时,您会得到一个相反的过程。您将读取一个unsigned char
bytes
数组。然后您知道第一个字节(即bytes[0]
)包含名称字段的位长。然后通过移位和屏蔽逐字节填充“lname”的字节。ETC....
祝你好运!