算法之赫夫曼编码(压缩文件解压文件)

1.赫夫曼编码基本介绍

(1)赫夫曼编码(Humffman Coding)是一种编码方式,属于一种程序算法,是赫夫曼树在电讯通信中的经典应用之一,广泛的用于数据文件压缩,其压缩率通常在20%~90%之间,赫夫曼编码是可变字长编码(VLC)的一种,Huffman于1952年提出一种编码方法,称为最佳编码.

传统编码:
(1)定长编码
加粗样式
(2)变长编码
在这里插入图片描述
赫夫曼编码原理剖析
(1)传输的字符串
在这里插入图片描述
(2)各个字符对应的个数
在这里插入图片描述
(3)按照上面字符出现的次数构建一颗赫夫曼树,次数作为权值
创建赫夫曼树的步骤参考上篇博客
(4)根据赫夫曼树,给各个字符,规定编码,向左的路径为0.向右的路径为1,编码如下
在这里插入图片描述
(5)按照上面的赫夫曼编码,输入的字符串对应的编码(注意这里我们使用的无损压缩)为
在这里插入图片描述
说明一下:
原来的长度是359,压缩了(359-133)/359 = 62.9%,此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀,不会造成匹配的多义性.

package com.self.dataStructure.huffmanCode;

import java.util.*;

public class HuffmanCodeDemo {
    //生成赫夫曼树对应的赫夫曼编码
    static Map<Byte,String> huffmanCoeds = new HashMap<Byte,String>();  //将赫夫曼编码存放在map中
    static StringBuilder stringBuilder = new StringBuilder();  //储存叶子节点的路径
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();

        /*List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);
        System.out.println("=======================");

        Node huffmanTree = createHuffmanTree(nodes);
        System.out.println(huffmanTree);
        System.out.println("=======================");
        huffmanTree.preOrder();

        System.out.println("=======================");
        Map<Byte, String> codes = getCodes(huffmanTree);
        System.out.println(codes);

        System.out.println("=========================");
        byte[] zip = zip(contentBytes, codes);
        System.out.println(Arrays.toString(zip));*/  //注释起来的这部分是测试使用  方便大家理解  我也放给大家看
        byte[] bytes = huffmanZip(contentBytes);
        System.out.println(Arrays.toString(bytes));  //完成的是数据的压缩
        
    }
    /**
     * @param bytes  原始字符串对应的字节数组
     * @return  经过赫夫曼编码处理后的字节数组
     */
    private static byte[] huffmanZip(byte[] bytes){
        List<Node> nodes = getNodes(bytes);
        Node huffmanTree = createHuffmanTree(nodes);
        Map<Byte, String> huffmanCodes = getCodes(huffmanTree);
        byte[] zip = zip(bytes, huffmanCodes);
        return zip;
    }
    /**
     * @param bytes  原始的字符串对应的byte[]
     * @param huffmanCoeds  生成的赫夫曼编码map
     * @return  返回赫夫曼编码处理后的byte[]
     */
    //编写一个方法,将字符串对应的byte[]数组,通过成成的赫夫曼编码表,返回一个赫夫曼编码压缩后的Byte[]
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCoeds){
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            stringBuilder.append(huffmanCoeds.get(b));
        }
        int length;
        if(stringBuilder.length() % 8 == 0){
            length = stringBuilder.length() / 8;
        }else{
            length = stringBuilder.length() / 8 + 1;
        }
        //创建压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[length];
        int index = 0;
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String strByte ;
            if(i+8 > stringBuilder.length()){
                strByte = stringBuilder.substring(i);
            }else{
                strByte = stringBuilder.substring(i, i + 8);
            }
            huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
            index ++;
        }
        return huffmanCodeBytes;
    }
    //为了调用方便,重载getCodes
    private static Map<Byte,String> getCodes(Node root){
        if(root == null){
            return null;
        }else{
            getCodes(root.left,"0",stringBuilder);
            getCodes(root.right,"1",stringBuilder);
        }
        return huffmanCoeds;
    }
    /** 将传入的node结点的所有叶子结点的赫夫曼树得到,并放入huffmanCodes中
     * @param node  传入节点
     * @param code  路径:左子结点是0,右子结点是1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node,String code,StringBuilder stringBuilder){
        StringBuilder builder = new StringBuilder(stringBuilder);
        builder.append(code);
        if(node != null){ //如果ndoe==null,不处理
            if(node.data == null){ //判断当前节点是叶子结点还是非叶子结点
                getCodes(node.left,"0",builder); //向左递归
                getCodes(node.right,"1",builder); //向右递归
            }else{  //说明是一个叶子结点
                huffmanCoeds.put(node.data,builder.toString());
            }
        }
    }
    private static List<Node> getNodes(byte[] bytes){
        ArrayList<Node> nodes = new ArrayList<>();
        HashMap<Byte, Integer> map = new HashMap<>();
        for (byte b : bytes) {
            Integer integer = map.get(b);
            if(integer == null){
                map.put(b,1);
            }else{
                map.put(b,integer + 1);
            }
        }
        Set<Map.Entry<Byte, Integer>> entrySet = map.entrySet();
        for (Map.Entry<Byte, Integer> entry : entrySet) {
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }
    //创建huffmanTree
    private static Node createHuffmanTree(List<Node> nodes){
        while (nodes.size() > 1){
            Collections.sort(nodes);
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parent = new Node(null,leftNode.weight + rightNode.weight);

            parent.left = leftNode;
            parent.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);
        }
        return nodes.get(0);
    }
}
class Node implements Comparable<Node>{
    Byte data;
    int weight;
    Node left;
    Node right;
    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }
    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }
    //前序遍历的方法
    public void preOrder(){
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
}

使用赫夫曼树进行解码:

 /**
     * @param huffmanCoeds  赫夫曼编码表
     * @param huffmanBytes  赫夫曼编码得到的字节数组
     * @return  原来的数组
     */
    private static byte[] decode(Map<Byte,String> huffmanCoeds,byte[] huffmanBytes){
        StringBuilder stringBuilder = new StringBuilder();
        //将byte数组转换成二进制字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            stringBuilder.append(byteToBitString(!flag,b));
        }
        //把赫夫曼编码进行调换
        HashMap<String, Byte> map = new HashMap<>();
        Set<Map.Entry<Byte, String>> entrySet = huffmanCoeds.entrySet();
        for (Map.Entry<Byte, String> entry : entrySet) {
            map.put(entry.getValue(),entry.getKey());
        }
        //创建list,存放byte
        ArrayList<Byte> list = new ArrayList<>();
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 0;
            boolean flag = true;
            Byte b = null;
            while (flag){
                String key = stringBuilder.substring(i,i + count);
                b = map.get(key);
                if(b == null){
                    count ++;
                }else{
                    flag = false;
                }
            }
            list.add(b);
            i += count;
        }
        //把list中的数据放入到数组中
        byte[] bytes = new byte[list.size()];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = list.get(i);
        }
        return bytes;
    }

    /**  将一个byte换成一个二进制的字符串
     * @param flag  标识是否需要补高位  如果是true,表示需要补高位,如果是false,表示不需要补高位 如果是最后一个字节 无需补高位
     * @param b 传入的byte
     * @return 该b对应的二进制的字符串(注意:按补码返回)
     */
    private static String byteToBitString(boolean flag,byte b){
        int temp = b;  //将b转换成int
        if (flag){
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);  //返回的是temp对应的二进制补码
        if(flag){
            return str.substring(str.length() - 8);
        }else {
            return str;
        }
    }

压缩文件:

 /**
     * @param srcFile  目标压缩文件的路径
     * @param dstFile  压缩之后文件的储存位置
     */
    public static void zipFile(String srcFile,String dstFile){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fis = new FileInputStream(srcFile);  //创建文件输入流
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[fis.available()];
            //读取文件
            fis.read(b);
            //z直接对源文件进行压缩
            byte[] huffmanZip = huffmanZip(b);
            fos = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutPutStream
            oos = new ObjectOutputStream(fos);
            oos.writeObject(huffmanZip);    //把赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanCodes);  //在解压的时候回用到

        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                fis.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }

解压文件:

public static void unZipFile(String zipFile,String dstFile){
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        FileOutputStream fos = null;
        try{
            fis = new FileInputStream(zipFile);
            ois = new ObjectInputStream(fis);
            byte[] huffmanBytes = (byte[]) ois.readObject();
            Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();

            byte[] bytes = decode(huffmanCodes, huffmanBytes);

            fos = new FileOutputStream(dstFile);
            fos.write(bytes);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                fos.close();
                ois.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

赫夫曼压缩文件注意事项;
(1)如果文件本身是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化,比如视频,ppt等文件
(2)赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件,文本文件)
(3)如果一个文件的重复数据不多,压缩效果也不会很明显.

附件:所有流程和测试代码

package com.self.dataStructure.huffmanCode;

import java.io.*;
import java.util.*;

public class HuffmanCodeDemo {
    //生成赫夫曼树对应的赫夫曼编码
    static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();  //将赫夫曼编码存放在map中
    static StringBuilder stringBuilder = new StringBuilder();  //储存叶子节点的路径
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();

        List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);
        System.out.println("=======================");

        Node huffmanTree = createHuffmanTree(nodes);
        System.out.println(huffmanTree);
        System.out.println("=======================");
        huffmanTree.preOrder();

        System.out.println("=======================");
        Map<Byte, String> codes = getCodes(huffmanTree);
        System.out.println(codes);

        System.out.println("=========================");
        byte[] zip = zip(contentBytes, codes);
        System.out.println(Arrays.toString(zip));  //注释起来的这部分是测试使用  方便大家理解  我也放给大家看
        byte[] bytes = huffmanZip(contentBytes);

        System.out.println("------------------------");
        byte[] decode = decode(codes, bytes);
        System.out.println(new String(decode));

        /*String srcFile = "D://1.png";
        String dstFile = "D://a.zip";
        zipFile(srcFile,dstFile);*/
        String srcFile = "D://a.zip";
        String dstFile = "D://2.png";
        unZipFile(srcFile,dstFile);
        System.out.println("压缩完成");
    }
    public static void unZipFile(String zipFile,String dstFile){
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        FileOutputStream fos = null;
        try{
            fis = new FileInputStream(zipFile);
            ois = new ObjectInputStream(fis);
            byte[] huffmanBytes = (byte[]) ois.readObject();
            Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();

            byte[] bytes = decode(huffmanCodes, huffmanBytes);

            fos = new FileOutputStream(dstFile);
            fos.write(bytes);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                fos.close();
                ois.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * @param srcFile  目标压缩文件的路径
     * @param dstFile  压缩之后文件的储存位置
     */
    public static void zipFile(String srcFile,String dstFile){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fis = new FileInputStream(srcFile);  //创建文件输入流
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[fis.available()];
            //读取文件
            fis.read(b);
            //z直接对源文件进行压缩
            byte[] huffmanZip = huffmanZip(b);
            fos = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutPutStream
            oos = new ObjectOutputStream(fos);
            oos.writeObject(huffmanZip);    //把赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanCodes);  //在解压的时候回用到

        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                fis.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
    /**
     * @param huffmanCoeds  赫夫曼编码表
     * @param huffmanBytes  赫夫曼编码得到的字节数组
     * @return  原来的数组
     */
    private static byte[] decode(Map<Byte,String> huffmanCoeds,byte[] huffmanBytes){
        StringBuilder stringBuilder = new StringBuilder();
        //将byte数组转换成二进制字符串
        for (int i = 0; i < huffmanBytes.length; i++) {
            byte b = huffmanBytes[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanBytes.length - 1);
            stringBuilder.append(byteToBitString(!flag,b));
        }
        //把赫夫曼编码进行调换
        HashMap<String, Byte> map = new HashMap<>();
        Set<Map.Entry<Byte, String>> entrySet = huffmanCoeds.entrySet();
        for (Map.Entry<Byte, String> entry : entrySet) {
            map.put(entry.getValue(),entry.getKey());
        }
        //创建list,存放byte
        ArrayList<Byte> list = new ArrayList<>();
        for (int i = 0; i < stringBuilder.length(); ) {
            int count = 0;
            boolean flag = true;
            Byte b = null;
            while (flag){
                String key = stringBuilder.substring(i,i + count);
                b = map.get(key);
                if(b == null){
                    count ++;
                }else{
                    flag = false;
                }
            }
            list.add(b);
            i += count;
        }
        //把list中的数据放入到数组中
        byte[] bytes = new byte[list.size()];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = list.get(i);
        }
        return bytes;
    }

    /**  将一个byte换成一个二进制的字符串
     * @param flag  标识是否需要补高位  如果是true,表示需要补高位,如果是false,表示不需要补高位 如果是最后一个字节 无需补高位
     * @param b 传入的byte
     * @return 该b对应的二进制的字符串(注意:按补码返回)
     */
    private static String byteToBitString(boolean flag,byte b){
        int temp = b;  //将b转换成int
        if (flag){
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);  //返回的是temp对应的二进制补码
        if(flag){
            return str.substring(str.length() - 8);
        }else {
            return str;
        }
    }
    /**
     * @param bytes  原始字符串对应的字节数组
     * @return  经过赫夫曼编码处理后的字节数组
     */
    private static byte[] huffmanZip(byte[] bytes){
        List<Node> nodes = getNodes(bytes);
        Node huffmanTree = createHuffmanTree(nodes);
        Map<Byte, String> huffmanCodes = getCodes(huffmanTree);
        byte[] zip = zip(bytes, huffmanCodes);
        return zip;
    }
    /**
     * @param bytes  原始的字符串对应的byte[]
     * @param huffmanCoeds  生成的赫夫曼编码map
     * @return  返回赫夫曼编码处理后的byte[]
     */
    //编写一个方法,将字符串对应的byte[]数组,通过成成的赫夫曼编码表,返回一个赫夫曼编码压缩后的Byte[]
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCoeds){
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes) {
            stringBuilder.append(huffmanCoeds.get(b));
        }
        int length;
        if(stringBuilder.length() % 8 == 0){
            length = stringBuilder.length() / 8;
        }else{
            length = stringBuilder.length() / 8 + 1;
        }
        //创建压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[length];
        int index = 0;
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String strByte ;
            if(i+8 > stringBuilder.length()){
                strByte = stringBuilder.substring(i);
            }else{
                strByte = stringBuilder.substring(i, i + 8);
            }
            huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
            index ++;
        }
        return huffmanCodeBytes;
    }
    //为了调用方便,重载getCodes
    private static Map<Byte,String> getCodes(Node root){
        if(root == null){
            return null;
        }else{
            getCodes(root.left,"0",stringBuilder);
            getCodes(root.right,"1",stringBuilder);
        }
        return huffmanCodes;
    }
    /** 将传入的node结点的所有叶子结点的赫夫曼树得到,并放入huffmanCodes中
     * @param node  传入节点
     * @param code  路径:左子结点是0,右子结点是1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node node,String code,StringBuilder stringBuilder){
        StringBuilder builder = new StringBuilder(stringBuilder);
        builder.append(code);
        if(node != null){ //如果ndoe==null,不处理
            if(node.data == null){ //判断当前节点是叶子结点还是非叶子结点
                getCodes(node.left,"0",builder); //向左递归
                getCodes(node.right,"1",builder); //向右递归
            }else{  //说明是一个叶子结点
                huffmanCodes.put(node.data,builder.toString());
            }
        }
    }
    private static List<Node> getNodes(byte[] bytes){
        ArrayList<Node> nodes = new ArrayList<>();
        HashMap<Byte, Integer> map = new HashMap<>();
        for (byte b : bytes) {
            Integer integer = map.get(b);
            if(integer == null){
                map.put(b,1);
            }else{
                map.put(b,integer + 1);
            }
        }
        Set<Map.Entry<Byte, Integer>> entrySet = map.entrySet();
        for (Map.Entry<Byte, Integer> entry : entrySet) {
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }
    //创建huffmanTree
    private static Node createHuffmanTree(List<Node> nodes){
        while (nodes.size() > 1){
            Collections.sort(nodes);
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parent = new Node(null,leftNode.weight + rightNode.weight);

            parent.left = leftNode;
            parent.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parent);
        }
        return nodes.get(0);
    }
}
class Node implements Comparable<Node>{
    Byte data;
    int weight;
    Node left;
    Node right;
    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }
    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }
    //前序遍历的方法
    public void preOrder(){
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null){
            this.right.preOrder();
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值