赫夫曼编码

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

image.png


image.png


image.png

package huffmancode;


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

public class HuffmanCode {
    public static void main(String[] args) {
        String content = "i like like like java do you like a java";
        byte[] contentBytes = content.getBytes();
        //压缩
        byte[] bytes = huffmanZip(contentBytes);
        //解压
        byte[] decode = decode(huffmanCodes, bytes);

        System.out.println(new String(decode));

        //压缩文件
        String srcFile = "d://test.txt";
        String dstFile = "d://test.zip";
        zipFile(srcFile,dstFile);

        //解压文件
        unZipFile("d://test.zip","d://upzip.png");

    }

    /**
     * 功能:文件解压
     * @param zipFile  需要解压的文件路径
     * @param dstFile  解压后文件存放路径
     */
    private static void unZipFile(String zipFile,String dstFile){
        InputStream is = null;
        ObjectInputStream ois = null;
        OutputStream os = null;

        try{
            is = new FileInputStream(zipFile);
            ois = new ObjectInputStream(is);
            //读取byte数组
            byte[] huffmanBytes = (byte[])ois.readObject();
            //读取赫夫曼编码表
            Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();
            //解码
            byte[] bytes = decode(huffmanCodes, huffmanBytes);
            //将bytes数组写入目标文件
            os = new FileOutputStream(dstFile);
            os.write(bytes);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try{
                os.close();
                ois.close();
                is.close();
            }catch (Exception e){
                System.out.println(e.getMessage());
            }

        }
    }
    /**
     * 功能:文件压缩
     * @param srcFile   需要压缩的文件全路径
     * @param dstFile   压缩后文件的存放路径
     */
    private static void zipFile(String srcFile,String dstFile){
        //创建输入流
        FileInputStream is = null;
        //创建输出流
        FileOutputStream os = null;
        ObjectOutputStream oos = null;

        try{
            is = new FileInputStream(srcFile);
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[is.available()];
            //读取文件
            is.read(b);
            //对文件进行压缩
            byte[] huffmanBytes = huffmanZip(b);
            //创建文件的输出流,存放压缩文件
            os = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutputStream
            oos = new ObjectOutputStream(os);
            //把赫夫曼编码后的字节数据写入压缩文件
            oos.writeObject(huffmanBytes);
            //把赫夫曼编码写入压缩文件
            oos.writeObject(huffmanCodes);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try{
                is.close();
                oos.close();
                os.close();
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
    }

    /**
     * 功能:完成对压缩数据的解码
     * @param huffmanCodes   赫夫曼编码表
     * @param huffmanBytes   赫夫曼编码的到的字节数组
     * @return 原来的字符串对应的数组
     */
    private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes){
        //得到HuffmanBytes对应的二进制的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //将byte数组转成二进制的字符串
        for(int i =0;i<huffmanBytes.length;i++){
            //判断是否为最后一个字节
            if (i == huffmanBytes.length-1){
                stringBuilder.append(byteToBitString(false,huffmanBytes[i]));
            }else {
                stringBuilder.append(byteToBitString(true,huffmanBytes[i]));
            }

        }
        //把字符串按照指定的和福安编码进行解码
        //把赫夫曼编码表进行调换
        Map<String,Byte> map = new HashMap<String,Byte>();
        for (Map.Entry<Byte,String> entry:huffmanCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        //创建一个集合存放byte
        List<Byte> list = new ArrayList<Byte>();

        for (int i =0;i<stringBuilder.length();){
            int count = 1;
            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 b[] = new byte[list.size()];
        for (int i =0;i<b.length;i++){
            b[i] = list.get(i);
        }
        return b;
    }

    /**
     * 功能:将一个byte转成一个二进制的字符串
     * @param flag 标志是否需要补高位
     * @param b 传入的byte
     * @return 传入byte对应的二进制字符串(补码)
     */
    private static String byteToBitString(boolean flag,byte b){
        int temp = b;
        if (flag){
            temp |= 256; //按位与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){
        //根据字节数组,得到Node结点集合
        List<Node> nodes = getNodes(bytes);
        //传入Node集合创建赫夫曼树
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        //根据赫夫曼树或去赫夫曼编码
        Map<Byte,String> huffmanCodes = getCodes(huffmanTreeRoot);
        //压缩
        byte[] huffmanCodeBytes = zip(bytes,huffmanCodes);
        return huffmanCodeBytes;
    }
    //编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩的后的byte[]
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        //利用huffmanCode将bytes转成赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes数组
        for (byte b:bytes){
            stringBuilder.append(huffmanCodes.get(b));
        }
        
        //len 存储stringBuilder需要的字节数组长度
        int len;
        if (stringBuilder.length()%8 == 0){
            len = stringBuilder.length() / 8;
        }else {
            len = stringBuilder.length() / 8 + 1;
        }

        //创建存储压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[len];
        int index = 0; //记录是第几个byte
        for (int i =0;i<stringBuilder.length();i += 8){  //因为每8位对应一个byte,所以步长为8
            String strByte;
            if (i+8 > stringBuilder.length()){
                strByte = stringBuilder.substring(i);
            }else {
                strByte = stringBuilder.substring(i,i+8);
            }
            //将strByte转成一个byte,放入到huffmanCodeBytes
            huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
            index++;
        }

        return huffmanCodeBytes;
    }

    //将赫夫曼编码表存放在Map<Byte,String>形式
    static  Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
    //在生成赫夫曼编码表,需要去拼接路径
    static StringBuilder stringBuilder = new StringBuilder();

    //为了调用方便,重载getCodes
    private static Map<Byte,String> getCodes(Node root){
        if (root == null){
            return null;
        }
        //处理root的左子树
        getCodes(root.left,"0",stringBuilder);
        //处理root的右子树
        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 stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2
        stringBuilder2.append(code);
        if (node != null){   //如果node == null 不处理
            //判断当前节点node是否为叶子节点
            if (node.data == null){  //非叶子节点
                //递归处理
                //向左递归
                getCodes(node.left,"0",stringBuilder2);
                //向右递归
                getCodes(node.right,"1",stringBuilder2);
            }else {  //叶子节点
                huffmanCodes.put(node.data,stringBuilder2.toString());
            }
        }
    }

    /**
     * 功能:根据传入的字节数组,转换成List<Node> (data->字节 weight->出现的次数)
     * @param bytes  字节数组
     * @return        List<Node>
     */
    private static List<Node> getNodes(byte[] bytes){
        //创建一个list
        ArrayList<Node> nodes = new ArrayList<Node>();
        //统计每个byte出现的次数
        HashMap<Byte, Integer> counts = new HashMap<Byte, Integer>();

        for (byte b : bytes){
            Integer count = counts.get(b);
            if (count == null){
                counts.put(b,1);
            }else {
                counts.put(b,count+1);
            }
        }

        //把每个map键值对转成Node,并加入到nodes集合中
        for (Map.Entry<Byte,Integer> entry: counts.entrySet()){
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        return nodes;
    }

    //通过list创建对应的赫夫曼树
    private static Node createHuffmanTree(List<Node> nodes){
        while (nodes.size() > 1){
            //排序
            Collections.sort(nodes);
            //取出第一棵最小的二叉树
            Node leftNode = nodes.get(0);
            //取出第二课最小的二叉树
            Node rightNode = nodes.get(1);
            //创建一个新的二叉树,它的根结点没有data,只有权值
            Node parentNode = new Node(null,leftNode.weight + rightNode.weight);
            parentNode.left = leftNode;
            parentNode.right = rightNode;
            //将已经处理的两棵二叉树从nodes中删除
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //将新的二叉树加入到nodes中
            nodes.add(parentNode);
        }

        return nodes.get(0);
    }
}


//创建Node,带数据和权值
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;
    }

    public int compareTo(Node o) {
        //从小到大排序
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "data:"+this.data +",weight:"+ this.weight;
    }

    //前序遍历
    public void preOrder(){
        //输出当前节点值
        System.out.println("data:"+this.data + ", weight:"+ this.weight);
        //向左遍历
        if (this.left != null){
            this.left.preOrder();
        }
        //向右遍历
        if (this.right != null){
            this.right.preOrder();
        }
    }
}
posted @ 2022-11-06 22:38  youmo~  阅读(22)  评论(0编辑  收藏  举报