這次帶給大家PHP實作Huffman編碼/解碼步驟詳解,PHP實作Huffman編碼/解碼的注意事項有哪些,下面就是實戰案例,一起來看一下。
本文就來用 PHP 來實作 Huffman 程式碼和解碼。
1. 編碼
字數統計
#Huffman編碼的第一步就是要統計文件中每個字元出現的次數,PHP的內建函數 count_chars() 就可以做到:
$input = file_get_contents('input.txt'); $stat = count_chars($input, 1);
建構Huffman樹
接下來根據統計結果構造Huffman樹,構造方法在Wikipedia 有詳細的描述。這裡用PHP寫了一個簡易版的:
$huffmanTree = []; foreach ($stat as $char => $count) { $huffmanTree[] = [ 'k' => chr($char), 'v' => $count, 'left' => null, 'right' => null, ]; } // 构造树的层级关系,思想见wiki:https://zh.wikipedia.org/wiki/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81 $size = count($huffmanTree); for ($i = 0; $i !== $size - 1; $i++) { uasort($huffmanTree, function ($a, $b) { if ($a['v'] === $b['v']) { return 0; } return $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 就會指向Huffman 樹的根節點
##根據Huffman樹產生編碼字典
有了Huffman 樹,就可以產生用於編碼的字典:function 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);
#寫檔案
運用字典將檔案內容編碼,並寫入文件。將Huffman編碼寫入檔案的有幾個注意的地方:將編碼字典和編碼內容一起寫入檔案後,就沒法區分他們的邊界了,因此需要在檔案開始寫入他們各自佔用的位元組數PHP提供的fwrite() 函數一次能寫入8-bit(一個位元組)或是8的整數倍個bit。但Huffman編碼中,一個字元可能只用 1-bit 表示,PHP不支援只往檔案中寫入 1-bit 這種操作。所以需要我們自行對編碼進行拼接,每湊齊 8-bit 才會寫入檔案。 每湊齊8-bit才寫入與第二條類似,最終形成的檔案大小一定是 8-bit 的整數倍。所以如果整個編碼的大小是8001-bit的話,還要在末尾補上7個0$dictString = serialize($dict); // 写入字典和编码各自占用的字节数 $header = pack('VV', strlen($dictString), strlen($input)); fwrite($outFile, $header); // 写入字典本身 fwrite($outFile, $dictString); // 写入编码的内容 $buffer = ''; $i = 0; while (isset($input[$i])) { $buffer .= $dict[$input[$i]]; while (isset($buffer[7])) { $char = bindec(substr($buffer, 0, 8)); fwrite($outFile, chr($char)); $buffer = substr($buffer, 8); } $i++; } // 末尾的内容如果没有凑齐 8-bit,需要自行补齐 if (!empty($buffer)) { $char = bindec(str_pad($buffer, 8, '0')); fwrite($outFile, chr($char)); } fclose($outFile);
解碼
Huffman編碼的解碼相對簡單:先讀取編碼字典,然後根據字典解碼出原始字元。 解碼過程有個問題要注意:由於我們在編碼過程中,在檔案結尾補齊了幾個0-bit,如果這些0-bit 在字典中剛好是某個字元的編碼時,就會造成錯誤的解碼。 所以解碼過程中,當已解碼的字元數達到文件長度時,就要停止解碼。<?php $content = file_get_contents('a.out'); // 读出字典长度和编码内容长度 $header = unpack('VdictLen/VcontentLen', $content); $dict = unserialize(substr($content, 8, $header['dictLen'])); $dict = array_flip($dict); $bin = substr($content, 8 + $header['dictLen']); $output = ''; $key = ''; $decodedLen = 0; $i = 0; while (isset($bin[$i]) && $decodedLen !== $header['contentLen']) { $bits = decbin(ord($bin[$i])); $bits = str_pad($bits, 8, '0', STR_PAD_LEFT); for ($j = 0; $j !== 8; $j++) { // 每拼接上 1-bit,就去与字典比对是否能解码出字符 $key .= $bits[$j]; if (isset($dict[$key])) { $output .= $dict[$key]; $key = ''; $decodedLen++; if ($decodedLen === $header['contentLen']) { break; } } } $i++; } echo $output;
試驗
我們將Huffman編碼Wiki頁的HTML代碼儲存到本地,進行Huffman編碼測試,試驗結果:#編碼前: 418,504 位元組編碼後: 280,127 位元組空間節省了33%,如果原文的重複內容較多,Huffman編碼節省的空間可以達到50%以上.除了文字內容,我們再嘗試將一個二進位檔案進行Huffman編碼,例如f.lux的安裝程序,試驗結果如下:
編碼前: 770,384 位元組編碼後: 773,076 位元組編碼後反而佔用了更大的空間,一方面是由於我們儲存字典時,並沒有做額外的處理,佔用了不少空間。另一方面,在二進位檔案中,各個字元出現的機率相對比較平均,無法發揮Huffman編碼的優勢。 相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章! 推薦閱讀:
以上是PHP實作Huffman編碼/解碼步驟詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!