英文半字节压缩编码技术
《信息论》的实验,比较有意思,实验原理如下:
对于一个通信系统来说,信息传输的有效性、可靠性、安全性和认证性是人们的主要目标。其中,信息传输的有效性指的是尽可能的使用较短的时间和较少的设备等资源来传送尽可能多的信息,而这一目的主要是通过信源编码这个环节来实现的。
虽然有许许多多不同的信源编码方法,但总的说来,信源编码主要是通过减少或消除信源的剩余度来提高传输效率的。而且,有时人们为了追求更高的传输效率,在满足实际需求的情况下,还允许在编译码过程中存在一定程度的失真,这就是所谓的有损压缩。当然,针对不同的应用要求,可以选择不同的压缩编码办法,为了方便理解和实现,针对一般的英文文本,可以设计一种半字节压缩编码方法来实现数据的压缩。
(一)有损处理
在一般英文文本中,除了大、小写英文字母外,还有多种不同的标点符号。为了达到在不影响文章大意的前提下,尽可能的减少需编码的符号数,以提高信息传输效率的目的,可采取这样的处理方法:
1) 所有的英文字母不区分大、小写(如:将所有的大写英文字母变成小写字母);
2) 保留标点符号:“,”、“。”、“?”“:”和“ ”;
3) 将“!”和“;”变为“。”,其他符号全部变成“ ”。
这样,原来的英文文本就变成了一个新的文本,该文本全部由26个英文字母和“,”、“。”、“?”、“:”以及“ ”这31种符号组成,而且,文章的大意并没有发生大的变化。可以认为这种失真是在允许的失真范围之内的。
(二)数据压缩
在计算机中,文本文件中的每个符号都是由8位的ASCII码所构成,共有256种取值的可能。既然经过上述有损处理后文件中只存在31种不同的符号,所以在压缩编码过程中只需对31种符号进行编码,就可以大大压缩文本文件的数据量。考虑到各字母以及符号出现的概率,并考虑码字的可分离性,可以采取以下的编码方法来进行数据的压缩:
1) 对于概率最大的15个符号分别编以“0000”~“1110”的码字:
符号 |
码字 |
符号 |
码字 |
符号 |
码字 |
符号 |
码字 |
空格 |
0000 |
e |
0100 |
l |
1000 |
s |
1100 |
a |
0001 |
f |
0101 |
n |
1001 |
t |
1101 |
c |
0010 |
h |
0110 |
o |
1010 |
u |
1110 |
d |
0011 |
i |
0111 |
r |
1011 |
|
|
2) 对于其余的16个符号分别编以“1111 0000”~“11111111”的码字。
符号 |
码字 |
符号 |
码字 |
符号 |
码字 |
符号 |
码字 |
, |
1111 0000 |
b |
1111 0100 |
m |
1111 1000 |
w |
1111 1100 |
. |
1111 0001 |
g |
1111 0101 |
p |
1111 1001 |
x |
1111 1101 |
? |
1111 0010 |
j |
1111 0110 |
q |
1111 1010 |
y |
1111 1110 |
: |
1111 0011 |
k |
1111 0111 |
v |
1111 1011 |
z |
1111 1111 |
这样,一些最经常出现的符号从原来的8bit变为4bit,达到了数据压缩的目的。
(三)译码
译码过程是编码的逆过程,解码后得到由这31种符号所组成的文本文件。
(四)半字节操作
在计算机中,所有对数据的操作都是以字节(8bit)为单位的,而我们的编码与解码都是以半个字节(4bti)为单位的,因此需要运用位操作来进行数据的控制:
1)编码过程中,若待编码字符为前15个符号之一,则写入其4bit码字;若待编码字符为后16个符号之一,则先写入前4bit“1111”,再写入后4bit相应的码字。写入过程中,由于每次向输出文件的写入是以字节(8bit)为单位的,故需每凑足8bit(1字节)执行一次向输出文件的写入操作。
2)译码过程中,每次读取4bit。若不为“1111”,则根据此次读取的4bit译为相应的前15个符号之一;若为“1111”,则再次读取4bit,并根据后4bit译为相应的后16个符号之一。读取过程中,由于每次向输入文件的读取是以字节(8bit)为单位的,故需将每次读取的8bit(1字节)分为两部分:前后各4bit。
代码如下:
有损压缩:
#include <stdio.h> #include <stdlib.h> void main() { FILE *fp, *lp; char ch; if((fp = fopen("aaa.txt","r")) == NULL) { printf("error!!!\n"); exit(0); } if((lp = fopen("bbb.txt","w")) == NULL) { printf("error!!!\n"); exit(0); } ch = fgetc(fp); while(ch != EOF) { if((ch != '!')&&(ch != ';')&&(ch != ',')&&(ch != '.')&&(ch != '?')&&(ch != ':') &&((ch<65)||((ch>90)&&(ch<97))||(ch>122))) ch = ' '; else if(ch=='!' || (ch==';')) ch = '.'; fputc(ch,lp); ch=fgetc(fp); } fclose(fp); fclose(lp); }
编码:
#include <stdio.h> #include <stdlib.h> static unsigned int outbfr; static FILE *outfile,*infile; static int outcnt; static int outbytecnt; static int inbytecnt; void init() { outbfr=0; outcnt=8; outbytecnt=0; inbytecnt=0; } void putbits(int val) { outbfr = (outbfr<<4)&255; outbfr |= val; outcnt -= 4; if (outcnt==0) { fputc(outbfr,outfile); outcnt = 8; outbytecnt++; } } void alignbits() { if (outcnt!=8) putbits(0); } void main() { char ch; int code; if((infile=fopen("bbb.txt","rb"))==NULL) {printf("cannot open infile!!!\n"); exit(0); } if((outfile=fopen("ccc.txt","wb"))==NULL) {printf("cannot open outfile!!!\n"); exit(0); } init(); ch=fgetc(infile); while(!feof(infile)) { inbytecnt++; switch(ch) { case ' ': code=0;break; case 'a': case 'A': code=1;break; case 'c': case 'C': code=2;break; case 'd': case 'D': code=3;break; case 'e': case 'E': code=4;break; case 'f': case 'F': code=5;break; case 'h': case 'H': code=6;break; case 'i': case 'I': code=7;break; case 'l': case 'L': code=8;break; case 'n': case 'N': code=9;break; case 'o': case 'O': code=10;break; case 'r': case 'R': code=11;break; case 's': case 'S': code=12;break; case 't': case 'T': code=13;break; case 'u': case 'U': code=14;break; case ',': putbits(15); code=0;break; case '.': putbits(15); code=1;break; case '?': putbits(15); code=2;break; case ':': putbits(15); code=3;break; case 'b': case 'B': putbits(15); code=4;break; case 'g': case 'G': putbits(15); code=5;break; case 'j': case 'J': putbits(15); code=6;break; case 'k': case 'K': putbits(15); code=7;break; case 'm': case 'M': putbits(15); code=8;break; case 'p': case 'P': putbits(15); code=9;break; case 'q': case 'Q': putbits(15); code=10;break; case 'v': case 'V': putbits(15); code=11;break; case 'w': case 'W': putbits(15); code=12;break; case 'x': case 'X': putbits(15); code=13;break; case 'y': case 'Y': putbits(15); code=14;break; case 'z': case 'Z': putbits(15); code=15;break; default : printf("error\n"); exit(0); } putbits(code); ch=fgetc(infile); } alignbits(); fclose(infile); fclose(outfile); printf("the compress rate is %d%%\n",(outbytecnt*100)/inbytecnt); }
解码:
#include <stdio.h> #include <stdlib.h> static unsigned int inbfr; static FILE *outfile,*infile; static int incnt; void init() { inbfr=0; incnt=8; } int getbits() { int code; int mask=255; code=inbfr>>4; inbfr=(inbfr<<4)&mask; incnt-=4; if(incnt==0) { inbfr=fgetc(infile); incnt=8; } return code; } void main() { int code; char ch; if((infile=fopen("ccc.txt","rb"))==NULL) { printf("cannot open infile!!!\n"); exit(0); } if((outfile=fopen("ddd.txt","wb"))==NULL) { printf("cannot open outfile!!!\n"); exit(0); } init(); inbfr=fgetc(infile); while(!feof(infile)) { code=getbits(); if(code!=15) { switch(code) { case 0: ch=' ';break; case 1: ch='a';break; case 2: ch='c';break; case 3: ch='d';break; case 4: ch='e';break; case 5: ch='f';break; case 6: ch='h';break; case 7: ch='i';break; case 8: ch='l';break; case 9: ch='n';break; case 10: ch='o';break; case 11: ch='r';break; case 12: ch='s';break; case 13: ch='t';break; case 14: ch='u'; } } else { code=getbits(); if(!feof(infile)) { switch(code) { case 0: ch=',';break; case 1: ch='.';break; case 2: ch='?';break; case 3: ch=':';break; case 4: ch='b';break; case 5: ch='g';break; case 6: ch='j';break; case 7: ch='k';break; case 8: ch='m';break; case 9: ch='p';break; case 10: ch='q';break; case 11: ch='v';break; case 12: ch='w';break; case 13: ch='x';break; case 14: ch='y';break; case 15: ch='z';break; default : printf("error\n"); exit(0); } } } fputc(ch,outfile); } fclose(infile); fclose(outfile); system("pause"); }
实验原理中提到将15个常用字符编写为4位(0000~1110),其余编写为8位(11110000~11111111),一次写文件操作需要8位。看程序之前还在疑惑,4位的字符之后需要等待下一个4位的字符凑齐8位才能进行写操作,但如果下一个字符是是8位的如何处理,如果继续等待,解码之后字符顺序就会错位。结果程序中给出的处理方法是4位字符之后补4个0,虽然是种方法,但有些浪费空间。
程序分为三部分,每个部分有一个main函数,可以合并成一个文件。