霍夫曼编码是一种数据压缩算法。zip压缩的核心是Huffman编码,在HTTP/2中,使用Huffman编码对HTTP头进行压缩。
本文将使用PHP来练习霍夫曼编码和解码。
1.编码
字数
霍夫曼编码的第一步是统计文档中每个字符的出现次数。PHP的内置函数count_chars()可以做到这一点:
$ input=file _ get _ contents(' input . txt ');$stat=count_chars($input,1);构造霍夫曼树
然后根据统计结果构造哈夫曼树,并在维基百科中详细描述了构造方法。这里有一个用PHP编写的简单版本:
$ HuffMantree=[];foreach($ stat as $ char=$ count){ $ HuffMantree[]=[' k '=chr($ char),' v'=$count,' left'=null,' right'=null,];}//构造树的层次关系。见wiki:https://zh.wikipedia.org/wiki/霍夫曼编码$ size=count($ huffmanTree);for($ I=0;我。==$ size-1;$i ) { uasort($huffmanTree,function ($a,$ b){ if($ a[' v ']==$ b[' v ']){ return 0;}返回$a['v'] $b['v']?-1 : 1;});$ a=array _ shift($ HuffMantree);$ b=array _ shift($ HuffMantree);$ HuffMantree[]=[' v '=$ a[' v ']$ b[' v '],' left'=$b,' right'=$a,];} $ root=current($ HuffMantree);经过计算,$root将指向霍夫曼树的根节点
根据霍夫曼树生成编码字典
使用霍夫曼树,您可以生成用于编码的字典:
函数buildDict($elem,$code=' ',$ dict){ if(isset($ elem[' k ']){ $ dict[$ elem[' k ']]=$ code;} else { buildDict($elem['left'],$code。0 ',$ dict);buildDict($elem['right'],$code。1 ',$ dict);} } $ dict=[];buildDict($root ' ',$ dict);写入文件
使用字典对文件内容进行编码,并将其写入文件。将霍夫曼编码写入文件时,有几点需要注意:
将代码字典和代码内容写入文件后,无法区分它们的边界,因此需要在文件开头写入它们占用的字节数
PHP提供的fwrite()函数可以一次写入8位(一个字节)或8位的整数倍。但是,在霍夫曼编码中,一个字符可能只能用1位来表示,PHP不支持只向文件中写入1位的操作。因此,我们需要自己拼接代码,并每8位将它们写入文件。
每8位写一次
与第二篇文章类似,最终文件大小必须是8位的整数倍。因此,如果整个代码的大小是8001位,则应该在末尾添加七个零
$ dict string=serialize($ dict);//编写字典和编码占用的字节数$ header=pack ('vv '、strlen ($ dictstring)、strlen($ input));fwrite($ Oftile,$ header);//编写字典本身fwrite($outFile,$ dict string);//写入编码内容$ buffer=$ I=0;while(isset($ input[$ I]){ $ buffer。=$ dict[$ input[$ I]];while(isset($ buffer[7]){ $ char=bindec(substr($ buffer,0,8));fwrite($ Oftile,chr($ char));$buffer=substr($buffer,8);} $ I;}//如果结尾没有收集8位,如果(!empty($ buffer)){ $ char=bindec(str _ pad($ buffer,8,' 0 ');fwrite($ Oftile,chr($ char));} fc lose($ Oftile);解码
霍夫曼编码的解码比较简单:先读编码字典,然后根据字典解码原始字符。
解码过程中出现了一个问题,需要注意:因为我们在编码过程中会在文件末尾填入几个0位,如果这几个0位恰好是字典中某个字符的编码,就会造成解码错误。
因此,在解码过程中,当解码字符的数量达到文档的长度时,有必要停止解码。
?PHP $ content=file _ get _ contents(' a . out ');//读取字典长度和编码内容长度$ header=unpack(' vdictlen/vcontentlen ',$ content);$ dict=unserialize(substr($ content,8,$ header[' dict len ']));$ dict=array _ flip($ dict);$bin=substr($content,8 $ header[' dict len ']);$ output=$ key=$ decodelen=0;$ I=0;while(isset($ bin[$ I])$ decodelen!==$ header[' content len ']){ $ bits=decbin(order($ bin[$ I]);$bits=str_pad($bits,8,' 0 ',STR _ PAD _ LEFT);for($ j=0;$j!==8;$j) {//每次拼接1位,都要和字典比较一下,看能不能解码字符$key。=$ bits[$ j];if(isset($ dict[$ key]){ $ output。=$ dict[$ key];$ key=$ decodedLenif($ decodelen==$ header[' content len ']){ break;} } } $ I;} echo $输出;试验
我们在本地保存了霍夫曼编码维基页面的HTML代码,并测试了霍夫曼编码。测试结果如下:
编码前: 418,504字节
编码后的: 280,127字节
节省空间33%,如果原文重复内容较多,霍夫曼编码可以节省50%以上。
除了文本内容,我们尝试对一个二进制文件进行霍夫曼编码,比如f.lux的安装程序测试结果如下:
编码前: 770,384字节
编码后的: 773,076字节
一方面,当我们存储字典时,我们不做额外的处理,这占用了大量的空间。另一方面,在二进制文件中,每个字符出现的概率相对平均,无法发挥霍夫曼编码的优势。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。