赫夫曼编码

一、概述

  1、赫夫曼编码也翻译为    哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法
  2、赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。

  3、赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
  4、赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码

二、原理

 

 

 

 

三、代码

  

  1 public class HuffmanCode {
  2     //1. 将赫夫曼编码表存放在 Map<Byte,String> 形式
  3     //   生成的赫夫曼编码表
  4     // 32=01, 97=100, 100=11000, 117=11001,
  5     // 101=1110, 118=11011, 105=101, 121=11010,
  6     // 106=0010, 107=1111, 108=000, 111=0011}
  7     static HashMap<Byte, String> huffmanCodes = new HashMap<>();
  8     //2. 在生成赫夫曼编码表示,需要去拼接路径, 定义一个StringBuilder 存储某个叶子结点的路径
  9     static StringBuilder sb = new StringBuilder();
 10 
 11     public static void main(String[] args) {
 12         String content = "i like like like java do you like a java";
 13         byte[] contentBytes = content.getBytes();
 14         System.out.println(Arrays.toString(contentBytes));
 15 
 16 
 17         //3.0测试压缩
 18         String src = "Uninstall.xml";
 19         String dst = "Uninstall.zip";
 20         //zipFile(src, dst);
 21         System.out.println("压缩文件完成");
 22 
 23         //4.0测试解压文件
 24         String zipFile = "Uninstall.zip";
 25         String dstFile = "Uninstall2.xml";
 26         unZipFile(zipFile, dstFile);
 27 
 28         /*2.0
 29         //缩减的压缩,解压过程
 30         byte[] huffmanCodesBytes = huffmanZip(contentBytes);
 31         System.out.println("压缩后的结果是:" + Arrays.toString(huffmanCodesBytes) + " 长度= " + huffmanCodesBytes.length);
 32 
 33         byte[] sourceBytes = decode(huffmanCodes, huffmanCodesBytes);
 34         System.out.println("原来的字符串=" + new String(sourceBytes));
 35         */
 36         /*
 37         //1.0分析过程
 38 
 39         //1.1获取根据单个字符ASII一样的统计个数的list集合
 40         ArrayList<Node> nodes = getNode(contentBytes);
 41         //System.out.println(nodes);
 42         //1.2创建赫夫曼树
 43         Node huffmanTreeRoot = createHuffmanTree(nodes);
 44         //1.3前序遍历赫夫曼树
 45         huffmanTreeRoot.preOrder();
 46         //1.4生成赫夫曼编码
 47         HashMap<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
 48         //System.out.println(huffmanCodes);
 49         //1.5测试
 50         byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
 51         System.out.println(Arrays.toString(huffmanCodeBytes));
 52         */
 53     }
 54 
 55     /**
 56      * @param src 你传入的希望压缩的文件的全路径
 57      * @param dst 我们压缩后将压缩文件放到哪个目录
 58      */
 59     private static void zipFile(String src, String dst) {
 60         FileInputStream is = null;
 61         FileOutputStream fos = null;
 62         ObjectOutputStream osw = null;
 63         try {
 64 
 65             is = new FileInputStream(src);
 66             //创建一个和源文件大小一样的byte[]
 67             byte[] b = new byte[is.available()];
 68             //读取文件
 69             is.read(b);
 70             //直接对源文件压缩
 71             byte[] huffmanBytes = huffmanZip(b);
 72             //创建文件的输出流, 存放压缩文件
 73             fos = new FileOutputStream(dst);
 74             //创建一个和文件输出流关联的ObjectOutputStream
 75             osw = new ObjectOutputStream(fos);
 76             //把 赫夫曼编码后的字节数组写入压缩文件
 77             osw.writeObject(huffmanBytes);
 78             //这里我们以对象流的方式写入 赫夫曼编码,是为了以后我们恢复源文件时使用
 79             //注意一定要把赫夫曼编码 写入压缩文件
 80             osw.writeObject(huffmanCodes);
 81 
 82         } catch (IOException e) {
 83             System.out.println(e.getMessage());
 84         } finally {
 85             try {
 86                 if (is != null)
 87                     is.close();
 88             } catch (IOException e) {
 89                 e.printStackTrace();
 90             }
 91             try {
 92                 if (osw != null)
 93                     osw.close();
 94             } catch (IOException e) {
 95                 e.printStackTrace();
 96             }
 97             try {
 98                 if (fos != null)
 99                     fos.close();
100             } catch (IOException e) {
101                 e.printStackTrace();
102             }
103         }
104     }
105 
106     /**
107      * @param zipFile 准备解压的文件
108      * @param dstFile 将文件解压到哪个路径
109      */
110     private static void unZipFile(String zipFile, String dstFile) {
111 
112         FileInputStream fis = null;
113         ObjectInputStream ois = null;
114         FileOutputStream fos = null;
115         try {
116             fis = new FileInputStream(zipFile);
117             ois = new ObjectInputStream(fis);
118             //读取byte数组  huffmanBytes
119             byte[] huffmanBytes = (byte[]) ois.readObject();
120             //读取赫夫曼编码表
121             HashMap<Byte, String> huffmanCodes = (HashMap<Byte, String>) ois.readObject();
122             //解码
123             byte[] bytes = decode(huffmanCodes, huffmanBytes);
124             //将bytes 数组写入到目标文件
125             fos = new FileOutputStream(dstFile);
126             //写数据到 dstFile 文件
127             fos.write(bytes);
128         } catch (Exception e) {
129             System.out.println(e.getMessage());
130         } finally {
131             try {
132                 if (fis != null) {
133                     fis.close();
134                 }
135             } catch (IOException e) {
136                 e.printStackTrace();
137             }
138             try {
139                 if (ois != null) {
140                     ois.close();
141                 }
142             } catch (IOException e) {
143                 e.printStackTrace();
144             }
145             try {
146                 if (fos != null) {
147                     fos.close();
148                 }
149             } catch (IOException e) {
150                 e.printStackTrace();
151             }
152         }
153     }
154 
155 
156     //完成数据的解压
157     //思路
158     //1. 将huffmanCodeBytes [-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
159     //   重写先转成 赫夫曼编码对应的二进制的字符串 "1010100010111..."
160     //2.  赫夫曼编码对应的二进制的字符串 "1010100010111..." =》 对照 赫夫曼编码  =》 "i like like like java do you like a java"
161 
162     //编写一个方法,完成对压缩数据的解码
163 
164     /**
165      * @param huffmanCodes 赫夫曼编码表 map
166      * @param huffmanBytes 赫夫曼编码得到的字节数组
167      * @return 就是原来的字符串对应的数组
168      */
169     private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
170 
171         //1. 先得到 huffmanBytes 对应的 二进制的字符串 , 形式 1010100010111...
172         StringBuilder stringBuilder = new StringBuilder();
173         //将byte数组转成二进制的字符串
174         for (int i = 0; i < huffmanBytes.length; i++) {
175             byte b = huffmanBytes[i];
176             //判断是不是最后一个字节
177             boolean flag = (i == huffmanBytes.length - 1);
178             stringBuilder.append(byteToBitString(!flag, b));
179         }
180         //把字符串安装指定的赫夫曼编码进行解码
181         //把赫夫曼编码表进行调换,因为反向查询 a->100 100->a
182         Map<String, Byte> map = new HashMap<String, Byte>();
183         for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
184             map.put(entry.getValue(), entry.getKey());
185         }
186 
187         //创建要给集合,存放byte
188         List<Byte> list = new ArrayList<>();
189         //i 可以理解成就是索引,扫描 stringBuilder
190         for (int i = 0; i < stringBuilder.length(); ) {
191             int count = 1; // 小的计数器
192             boolean flag = true;
193             Byte b = null;
194 
195             while (flag) {
196                 //1010100010111...
197                 //递增的取出 key 1
198                 String key = stringBuilder.substring(i, i + count);//i 不动,让count移动,指定匹配到一个字符
199                 b = map.get(key);
200                 if (b == null) {//说明没有匹配到
201                     count++;
202                 } else {
203                     //匹配到
204                     flag = false;
205                 }
206             }
207             list.add(b);
208             i += count;//i 直接移动到 count
209         }
210         //当for循环结束后,我们list中就存放了所有的字符  "i like like like java do you like a java"
211         //把list 中的数据放入到byte[] 并返回
212         byte b[] = new byte[list.size()];
213         for (int i = 0; i < b.length; i++) {
214             b[i] = list.get(i);
215         }
216         return b;
217 
218     }
219 
220     /**
221      * 将一个byte 转成一个二进制的字符串, 如果看不懂,可以参考我讲的Java基础 二进制的原码,反码,补码
222      *
223      * @param b    传入的 byte
224      * @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
225      * @return 是该b 对应的二进制的字符串,(注意是按补码返回)
226      */
227     private static String byteToBitString(boolean flag, byte b) {
228         //使用变量保存 b
229         int temp = b;//将 b 转成 int
230         //如果是正数我们还存在补高位
231         if (flag) {
232             temp |= 256;//按位与 256  1 0000 0000  | 0000 0001 => 1 0000 0001
233         }
234         String str = Integer.toBinaryString(temp);
235         if (flag) {
236             return str.substring(str.length() - 8);
237         } else {
238             return str;
239         }
240     }
241     //使用一个方法,将前面的方法封装起来,便于我们的调用.
242 
243     /**
244      * @param bytes 原始的字符串对应的字节数组
245      * @return 是经过 赫夫曼编码处理后的字节数组(压缩后的数组)
246      */
247     private static byte[] huffmanZip(byte[] bytes) {
248         ArrayList<Node> nodes = getNode(bytes);
249         //根据 nodes 创建的赫夫曼树
250         Node huffmanTreeRoot = createHuffmanTree(nodes);
251         //对应的赫夫曼编码(根据 赫夫曼树)
252         HashMap<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
253         //根据生成的赫夫曼编码,压缩得到压缩后的赫夫曼编码字节数组
254         byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
255         return huffmanCodeBytes;
256     }
257 
258     /**
259      * 编写一个方法,将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码 压缩后的byte[]
260      *
261      * @param bytes        这时原始的字符串对应的 byte[]
262      * @param huffmanCodes 生成的赫夫曼编码map
263      * @return 返回赫夫曼编码处理后的 byte[]
264      * 举例: String content = "i like like like java do you like a java"; =》 byte[] contentBytes = content.getBytes();
265      * 返回的是 字符串 "1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000101111111100110001001010011011100"
266      * => 对应的 byte[] huffmanCodeBytes  ,即 8位对应一个 byte,放入到 huffmanCodeBytes
267      * huffmanCodeBytes[0] =  10101000(补码) => byte  [推导  10101000=> 10101000 - 1 => 10100111(反码)=> 11011000= -88 ]
268      * huffmanCodeBytes[1] = -88
269      */
270     private static byte[] zip(byte[] bytes, HashMap<Byte, String> huffmanCodes) {
271         StringBuilder stringBuilder = new StringBuilder();
272         for (byte b : bytes) {
273             stringBuilder.append(huffmanCodes.get(b));
274         }
275         //System.out.println(stringBuilder);
276         //统计返回  byte[] huffmanCodeBytes 长度
277         //一句话 int len = (stringBuilder.length() + 7) / 8;
278         int len;
279         if (stringBuilder.length() % 8 == 0) {
280             len = stringBuilder.length() / 8;
281         } else {
282             len = stringBuilder.length() / 8 + 1;
283         }
284         byte[] huffmanCodeBytes = new byte[len];
285         int index = 0;
286         for (int i = 0; i < stringBuilder.length(); i += 8) {
287             String b;
288             if (i + 8 > stringBuilder.length()) {
289                 b = stringBuilder.substring(i);
290             } else {
291                 b = stringBuilder.substring(i, i + 8);
292             }
293             //将strByte 转成一个byte,放入到 huffmanCodeBytes
294             huffmanCodeBytes[index] = (byte) Integer.parseInt(b, 2);
295             index++;
296         }
297         return huffmanCodeBytes;
298     }
299 
300     private static HashMap<Byte, String> getCodes(Node root) {
301         if (root == null) {
302             return null;
303         }
304         //处理root的左子树
305         getCodes(root.left, "0", sb);
306         //处理root的右子树
307         getCodes(root.right, "1", sb);
308         return huffmanCodes;
309     }
310 
311     /**
312      * 功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCodes集合
313      *
314      * @param node 传入结点
315      * @param code 路径: 左子结点是 0, 右子结点 1
316      * @param sb   用于拼接路径
317      */
318     private static void getCodes(Node node, String code, StringBuilder sb) {
319         StringBuilder sbb = new StringBuilder(sb);
320         sbb.append(code);
321         if (node != null) {
322             //判断当前node 是叶子结点还是非叶子结点
323             if (node.data == null) {//非叶子结点,不存数据data
324                 //向左递归
325                 getCodes(node.left, "0", sbb);
326                 //向右递归
327                 getCodes(node.right, "1", sbb);
328             } else {//说明是一个叶子结点
329                 //就表示找到某个叶子结点的最后
330                 huffmanCodes.put(node.data, sbb.toString());
331             }
332         }
333     }
334 
335     //可以通过List 创建对应的赫夫曼树
336     private static Node createHuffmanTree(ArrayList<Node> nodes) {
337 
338         while (nodes.size() > 1) {
339             //每次添加完节点,并且删除原来的节点后,是乱序的,只能每次循环都要排序
340             Collections.sort(nodes);
341             //取出第一颗最小的二叉树
342             Node left = nodes.get(0);
343             //取出第二颗最小的二叉树
344             Node right = nodes.get(1);
345             //创建一颗新的二叉树,它的根节点 没有data, 只有权值
346             Node parent = new Node(null, left.weight + right.weight);
347 
348             parent.left = left;
349             parent.right = right;
350             //将已经处理的两颗二叉树从nodes删除
351             nodes.remove(left);
352             nodes.remove(right);
353             //将新的二叉树,加入到nodes
354             nodes.add(parent);
355         }
356         //nodes 最后的结点,就是赫夫曼树的根结点
357         return nodes.get(0);
358     }
359 
360     /**
361      * @param bytes 接收字节数组
362      * @return 返回的就是 List 形式   [Node[date=97 ,weight = 5], Node[]date=32,weight = 9]......],
363      */
364     private static ArrayList<Node> getNode(byte[] bytes) {
365         HashMap<Byte, Integer> map = new HashMap<>();
366         for (byte b : bytes) {
367             Integer i = map.get(b);
368             if (i == null) {
369                 map.put(b, 1);
370             } else
371                 map.put(b, ++i);
372         }
373         ArrayList<Node> list = new ArrayList<>();
374         for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
375             list.add(new Node(entry.getKey(), entry.getValue()));
376         }
377 
378         return list;
379     }
380 }
381 
382 class Node implements Comparable<Node> {
383     // 存放数据(字符)本身,比如'a' => 97 ' ' => 32
384     Byte data;
385     //权值, 表示字符出现的次数
386     int weight;
387     Node left;
388     Node right;
389 
390     public Node(Byte data, int weight) {
391         this.data = data;
392         this.weight = weight;
393     }
394 
395 
396     @Override
397     public String toString() {
398         return "Node{" +
399                 "data=" + data +
400                 ", weight=" + weight +
401                 '}';
402     }
403 
404     @Override
405     public int compareTo(Node o) {
406         return this.weight - o.weight;
407     }
408 
409     public void preOrder() {
410         System.out.println(this);
411         if (this.left != null) {
412             this.left.preOrder();
413         }
414         if (this.right != null) {
415             this.right.preOrder();
416         }
417     }
418 }

 

posted @ 2019-09-10 20:59  hyunbar  阅读(681)  评论(0编辑  收藏  举报