数据结构 - 哈夫曼树及哈夫曼编码:压缩、解压

什么是哈夫曼树?

哈夫曼树(Huffman Tree):给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度(WPL)达到最小,这样的二叉树也称为最优二叉树

一棵带权二叉树:

在这里插入图片描述

这里有几个概念:

  • 路径:从一个结点到另一个结点经过的所有结点为两结点间的路径,如上图结点A到结点8的路径为:A-B-C-D-8

  • 路径长度:树中一个结点到另一个结点的经过的边数,A到结点8的路径长度为4

  • 权值:对于树的结点,可以定义一个有某种意义的数值为权重值
    如结点为商品,我们就可以设置权值为商品价格,权值根据需求定义

  • 结点的带权路径长度(WPL):对于某个结点,树的根结点到该结点的路径长度和该结点权重的乘积,即是该结点的带权路径长度,结点8的带权路径长度为32

哈夫曼树即是所有叶子结点的带权路径长度之和最小的树

上面的带权二叉树的带权路径长度为:
WPL = 413+48+329+21+26+33+3*7=215
很明显这带权路径长度不是最小的,即上面的带权二叉树不是哈夫曼树

如何实现带权路径长度最小?把权值较大的结点离根较近


带权路径长度最小

具体步骤如下:

  1. 将树叶子结点存储,并根据权值排序(以从小到大为例),把每个结点看成一个二叉树,权值即为该二叉树根节点的权值

在这里插入图片描述

  1. 取出权值最小的两棵树组成一棵新树,并将这两棵树的根节点权值相加作为新二叉树的根节点权值,把这棵新树加入数组,然后排序

在这里插入图片描述

  1. 后续就是重复上面的步骤,如下一步就是根节点为4的二叉树与根节点为6的二叉树组成新树

在这里插入图片描述

  1. 最终结果:数组仅有一个结点,即哈夫曼树的根结点

在这里插入图片描述


Java实现哈夫曼树

树结点类:

  • 权值为value属性
  • 继承Comparable<Node>接口,实现结点比较方法this.value - o.value,从小到大排序
  • 前序遍历方法:先打印当前结点,再循环遍历左子树,再循环遍历右子树
//树的结点,实现排序方法
class Node implements Comparable<Node>{
    //结点权值
    int value;
    //左子结点
    Node left;
    //右子结点
    Node right;


    //前序遍历
    public void preOrder(){
        System.out.print(this.value+" ");
        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }


    public Node(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node{" +
                "value=" + value +
                '}';
    }

    @Override
    public int compareTo(Node o) {
        //比较权值
        //表示从小到大排序
        return this.value - o.value;
    }
}

哈夫曼树:用Java完成了上面的步骤

public class HuffmanTree {

    public static void main(String[] args) {
        int[] arr = {13,7,8,3,29,6,1};
        Node root = createHuffmanTree(arr);

        preOrder(root);
    }

    /**
     * 前序遍历哈夫曼树
     * @param root 根结点
     */
    public static void preOrder(Node root){
        if (root != null){
            System.out.println("=== 哈夫曼树前序遍历 ===");
            root.preOrder();
        }
        else {
            System.out.println("=== 哈夫曼树为空 ===");
        }
    }


    /**
     * 创建哈夫曼树
     * @param arr 对应的数组
     */
    public static Node createHuffmanTree(int[] arr){
        //1.遍历arr数组
        //2.将arr每个元素构成一个Node
        //3.将Node放入List
        List<Node> nodes = new ArrayList<>();

        for (int value : arr){
            nodes.add(new Node(value));
        }

        //循环处理以下步骤
        while (nodes.size() > 1){
            //从小到大排序
            Collections.sort(nodes);

            //取出根结点权值最小的两颗二叉树(结点)
            //最小的结点(二叉树)
            Node leftNode = nodes.get(0);
            //第二下的结点(二叉树)
            Node rightNode = nodes.get(1);

            //构建新的二叉树,根结点的权值为左右子结点的权值和
            Node parent = new Node(leftNode.value + rightNode.value);
            parent.left = leftNode;
            parent.right = rightNode;

            //删除取出的两个结点
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将parent加入nodes,构成新的二叉树
            nodes.add(parent);
            Collections.sort(nodes);

        }

        //最终nodes仅一个结点,为哈夫曼树的根结点
        return nodes.get(0);

    }
}

测试结果,哈夫曼树的前序遍历:

在这里插入图片描述


哈夫曼树有什么用?哈夫曼编码

哈夫曼编码是一种编码方式,属于一种算法,是电讯通信的经典应用之一,广泛用于数据文件的压缩,其压缩率通常在20%~90%

关于编码方式

定长编码

我们知道通信是二进制通信,要传输字符串Hello World Hello Hello World
需要以下步骤:

  • 字符串分割为字符数组,所有的字符解析为ASCII码
    根据ASCII码:H-72,e-101,l-108,o-111,W-87,r-114,d-100,空格-32
Hello World Hello Hello World
=>
(Hello)72 101 108 108 111 32
(World)87 111 114 108 100 32 
(Hello)72 101 108 108 111 32
(Hello)72 101 108 108 111 32 
(World)87 111 114 108 100
  • 将ASCII码转为二进制数:也就是十进制转二进制
01001000 01100101 01101100 01101100 01101111 00100000
01010111 01101111 01110010 01101100 01100100 00100000
01001000 01100101 01101100 01101100 01101111 00100000
01001000 01100101 01101100 01101100 01101111 00100000
01010111 01101111 01110010 01101100 01100100 

虽然这样传输是可以达成目标的,但是太浪费了,为了传输29个字符,需要传输29*8=232个二进制数

变长编码

对于字符串Hello World Hello Hello World,各个字符出现次数 H:3,e:3,l:8,o:5,W:2,r:2,d:2,空格:4
根据出现次数编码,出现次数越多编码越小:

可以自定义编码规则为:

l=0,o=1,空格=10,H=11,e=100,r=101,d=110,W=111

字符串对应的编码为:1110000110111…

这样编码是简便了,但是存在一个问题:无法解码,11可以解码为H,也可以解码为ll

前缀编码:符号字符的编码不能是其他字符编码的前缀要求的编码

哈夫曼编码

对于字符串Hello World Hello Hello World,各个字符出现次数 H:3,e:3,l:8,o:5,W:2,r:2,d:2,空格:4

根据这些字符构建一颗哈夫曼树,以字符出现的次数为权值

关于构建哈夫曼树的步骤上面解释了,这里直接编程:

树结点实体类:新增data、weight属性

//哈夫曼树结点
class Node implements Comparable<Node>{
    //存放数据本身,'a' => 97
    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+"]";
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);

        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }

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

构建哈夫曼树方法:

  • 将字符数组及出现次数封装成结点对象,存入列表
  • 取出列表中的结点,构成哈夫曼树
  • 前序遍历打印
public class HeffumanCode {

    public static void main(String[] args) {
        String content = "Hello World Hello Hello World";
        byte[] contentBytes = content.getBytes();//29个字符
        
        List<Node> nodes = getNodes(contentBytes);

        System.out.println(nodes);

        System.out.println("=== 哈夫曼树 ===");
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        huffmanTreeRoot.preOrder();
    }
    //前序遍历
    private static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }
        else {
            System.out.println("=== 哈夫曼树为空 ===");
        }
    }

	//将字符数组及出现次数封装成结点对象存入列表
    private static List<Node> getNodes(byte[] bytes){
        List<Node> list = new ArrayList<>();
        //遍历bytes,统计每一个byte出现的次数,存储每一个byte,如[c,3]
        Map<Byte, Integer> map = new HashMap<>();

        for (byte b : bytes){
            Integer count = map.get(b);
            //map没有该字符
            if (count == null){
                map.put(b,1);
            }
            //map已经存在该字符,次数+1
            else {
                map.put(b,count + 1);
            }
        }
        //for循环结束,map中统计了字符出现次数
        //将每一个键值对转成Node对象,加入集合
        for (Map.Entry<Byte,Integer> entry:map.entrySet()){
            list.add(new Node(entry.getKey(),entry.getValue()));
        }

        return list;
    }


    //通过list创建哈夫曼树
    private static Node createHuffmanTree(List<Node> nodes){

		//当结点为1,即为哈夫曼树的根结点
        while (nodes.size() > 1){

            //排序:从小到大
            Collections.sort(nodes);

            //取出最小、倒数第二小的二叉树
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);

            //创建新的二叉树,根节点没有data,只有权值
            Node parent = new Node(null,leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            //移除已经处理的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将新的二叉树加入nodes
            nodes.add(parent);
        }

        //返回根结点
        return nodes.get(0);

    }
 }   

前序遍历打印:

在这里插入图片描述

这棵哈夫曼树为左树:实际上右树也是哈夫曼树,因为存在很多相同权值的结点,就会有不同的排序方式

在这里插入图片描述

规定编码

既然已经有了哈夫曼树,我们可以自定义一些规则来规定编码

向左的路径为0,向右的路径为1

在这里插入图片描述

根据上面打印的前序结果,可以知道这些字符

在这里插入图片描述

那么得到的编码规则为:

32(空格)=011, 114(r)=1100, 100(d)=1101, 
101(e)=001, 87(W)=000, 72(H)=010, 108(l)=10, 111(o)=111

根据哈夫曼编码规则表可以将字符串Hello World Hello Hello World翻译成二进制串

01000110101110110001111100101101011010001101011101101000110101110110001111100101101

然后每隔8位截取,然后翻译成十进制,就能得到数组:

[70, -69, 31, 45, 104, -41, 104, -41, 99, -27, 5]

相比于最初一共有29个字符的数组,这仅有11个字符,这就是哈夫曼编码的价值


Java实现哈夫曼编码

具体的步骤:

  • 将字符数组封装成哈夫曼树结点对象List<Node> getNodes(byte[] bytes)
  • 通过结点对象,创建哈夫曼树Node createHuffmanTree(List<Node> nodes)
  • 根据哈夫曼树创建哈夫曼编码表Map<Byte,String> getCodes(Node node)
  • 通过哈夫曼编码表对原始数组进行压缩byte[] zip(byte[] bytes ,Map<Byte,String> huffmanCodes)

因为步骤较多,就用了huffmanZip封装方法处理了这些步骤


public class HeffumanCode {

    public static void main(String[] args) {
                String content = "Hello World Hello Hello World";
        byte[] contentBytes = content.getBytes();//29

        List<Node> nodes = getNodes(contentBytes);

        System.out.println(nodes);

        System.out.println("=== 哈夫曼树 ===");
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        huffmanTreeRoot.preOrder();

        byte[] huffmanCodeBytes = huffmanZip(contentBytes);
        System.out.println("压缩后的结果:"+Arrays.toString(huffmanCodeBytes));
    }



    //将字符数组及出现次数封装成结点对象存入列表
    private static List<Node> getNodes(byte[] bytes){
        List<Node> list = new ArrayList<>();
        //遍历bytes,统计每一个byte出现的次数,存储每一个byte,如[c,3]
        Map<Byte, Integer> map = new HashMap<>();

        for (byte b : bytes){
            Integer count = map.get(b);
            //map没有该字符
            if (count == null){
                map.put(b,1);
            }
            //map已经存在该字符,次数+1
            else {
                map.put(b,count + 1);
            }
        }
        //for循环结束,map中统计了字符出现次数
        //将每一个键值对转成Node对象,加入集合
        for (Map.Entry<Byte,Integer> entry:map.entrySet()){
            list.add(new Node(entry.getKey(),entry.getValue()));
        }

        return list;
    }


    //通过list创建哈夫曼树
    private static Node createHuffmanTree(List<Node> nodes){

        //当结点为1,即为哈夫曼树的根结点
        while (nodes.size() > 1){

            //排序:从小到大
            Collections.sort(nodes);

            //取出最小、倒数第二小的二叉树
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);

            //创建新的二叉树,根节点没有data,只有权值
            Node parent = new Node(null,leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            //移除已经处理的二叉树
            nodes.remove(leftNode);
            nodes.remove(rightNode);

            //将新的二叉树加入nodes
            nodes.add(parent);
        }

        //返回根结点
        return nodes.get(0);

    }


    //生成哈夫曼树对应的哈夫曼对应的编码表,重载方法
    //1. 编码表存放在Map
    static Map<Byte,String> huffmanCodes = new HashMap<>();
    //2.生成哈夫曼编码表时,需要拼接路径,定义一个StringBuilder存储叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    private static Map<Byte,String> getCodes(Node node){
        if (node == null){
            System.out.println("=== 结点为空 ==");
        }
        //处理root的左子树
        getCodes(node.left,"0",stringBuilder);
        //处理右子树
        getCodes(node.right,"1",stringBuilder);
        return huffmanCodes;
    }

    /**
     * 将传入的结点Node的所有叶子结点的哈夫曼编码得到,存入huffmanCodes
     * @param node 传入结点,从根结点开始
     * @param code 路径:左子结点是0,右子结点是1
     * @param sb 用于拼接路径
     */
    private static void getCodes(Node node,String code,StringBuilder sb){
        StringBuilder stringBuilder1 = new StringBuilder(sb);
        //将code加入stringBuilder1
        stringBuilder1.append(code);

        if (node != null){
            //判断当前结点是叶子结点还是非叶子结点
            if (node.data == null){
                //为空为非叶子结点,递归处理
                //左递归,code=0
                getCodes(node.left,"0",stringBuilder1);
                //向右递归
                getCodes(node.right,"1",stringBuilder1);
            }
            else {
                //叶子结点,表示找到了最后
                huffmanCodes.put(node.data,stringBuilder1.toString());
            }

        }
    }

    /**
     * 封装哈夫曼编码,便于调用
     * @param bytes 原始字符串对应的字符数组
     * @return 经过哈夫曼编码处理后的字节数组(压缩后的数组)
     */
    private static byte[] huffmanZip(byte[] bytes){
        //将字符数组封装成哈夫曼树结点对象
        List<Node> nodes = getNodes(bytes);
        //通过结点对象,创建哈夫曼树
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        //根据哈夫曼树创建哈夫曼编码表
        Map<Byte, String> map = getCodes(huffmanTreeRoot);
        //通过哈夫曼编码表对原始数组进行压缩
        byte[] huffmanCodeBytes = zip(bytes, map);

        return huffmanCodeBytes;
    }



    /**
     * 将字符串对应的byte数组通过哈夫曼编码表返回一个哈夫曼编码 压缩后的 byte数组
     * @param bytes 原始字符数组
     * @param huffmanCodes 生成的哈夫曼编码表
     * @return 返回哈夫曼编码处理后的数组
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        //1.先用哈夫曼编码表将bytes转成 哈夫曼编码 对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes
        for (byte b : bytes){
            stringBuilder.append(huffmanCodes.get(b));
        }
        //01000110101110110001111100101101011010001101011101101000110101110110001111100101101
        System.out.println(stringBuilder);

        //将字符串转成byte数组
        int len;
        //统计返回的数组长度
        if (stringBuilder.length() / 8 == 0){
            len = stringBuilder.length() / 8;
        }
        else {
            len = stringBuilder.length() / 8 + 1;
        }

        //创建数组
        byte[] by = new byte[len];
        int index = 0;
        //步长为8
        for (int i = 0;i < stringBuilder.length();i += 8){
            String strByte;
            if (i+8 > stringBuilder.length()) {
                //不够8位
                strByte = stringBuilder.substring(i);
            }
            else {
                //每8位放入数组
                strByte = stringBuilder.substring(i, i + 8);
            }
            //将strByte转成一个byte,放入by
            by[index] = (byte) Integer.parseInt(strByte,2);
            index++;
        }
        //循环结束,得到byte数组
        return by;
    }
}







//哈夫曼树结点
class Node implements Comparable<Node>{
    //存放数据本身,'a' => 97
    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+"]";
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);

        if (this.left != null){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }

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

结果:

在这里插入图片描述


哈夫曼编码:解压

把字符串Hello World Hello Hello World根据哈夫曼编码压缩成了:

[70, -69, 31, 45, 104, -41, 104, -41, 99, -27, 5]

这个十进制数组需要自定义解压方法,不然无法解析出我们的字符串

思路:根据上面的哈夫曼编码规则表解压

具体的步骤:

  1. huffmanCodeBytes[70, -69, 31, 45, 104, -41, 104, -41, 99, -27, 5]重新转成哈夫曼编码对应的二进制字符串010001101011101100011111001011...

  2. 二进制字符串查看哈夫曼编码表转成字符串Hello World Hello Hello World

010(H) 001(e) 10(l) 10(l) 111(o) 0110001111100101101011010001101011101101000110101110110001111100101101

因为压缩的结果是[70, -69, 31, 45, 104, -41, 104, -41, 99, -27, 5],所以我们需要先转成二进制字符串,如果压缩的结果就是二进制字符串就可以直接解压


    //完成数据的解压
    //1.将huffmanCodeBytes[70, -69, 31, 45, 104, -41, 104, -41, 99, -27, 5]
    //  重新转成哈夫曼编码对应的二进制字符串010001101011101100011111001011...
    //2. 二进制字符串 根据对应的哈夫曼编码表 转成字符串Hello World Hello Hello World

    /**
     * 将一个byte转成二进制字符串
     * @param flag 标识是否需要补高位
     * @param b 需要转换的字符
     * @return 是该b对应的二进制字符串(按补码返回)
     */



    private static String byteToBitString(boolean flag,byte b) {
        //使用变量保存b
        int temp = b;

        if (flag) {
            //按位或256:temp=1 -> 1 0000 0000 | 0000 0001  -> 1 0000 0001
            temp |= 256;
        }
        //返回的是temp对应的二进制补码
        String binaryString = Integer.toBinaryString(temp);
        if (flag) {
            return binaryString.substring(binaryString.length() - 8);
        } else {
            return binaryString;
        }

    }


    /**
     * 对压缩数据的解码
     * @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++){
            boolean flag = (i == huffmanBytes.length -1);
            stringBuilder.append(byteToBitString(!flag,huffmanBytes[i]));
        }
  

        //把字符串按照指定的哈夫曼编码进行解码

        //将哈夫曼编码表反转
        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        //创建list存放byte
        List<Byte> list = new ArrayList<>();
        //扫描stringBuilder,在编码表中找到对应的二进制字符串就存入list
        for (int i = 0;i < stringBuilder.length();){
            int count = 1;
            boolean flag = true;
            Byte b = null;

            while (flag){
                //取出一个二进制字符
                //i不动,让count动,直到匹配到一个字符
                String key = stringBuilder.substring(i,i+count);
                b = map.get(key);
                if (b == null){
                    //没有匹配到
                    count++;
                }
                else {
                    //匹配到了,退出while循环
                    flag = false;
                }
            }
            list.add(b);
            //移动到匹配到的位置
            i += count;
        }
        //for循环结束后,list存放字符串所有的字符
        //把list中的数据取出放入byte[]
        byte[] b = new byte[list.size()];
        for (int i = 0;i < list.size();i++){
            b[i] = list.get(i);
        }

        return b;
    }

测试:

    public static void main(String[] args) {
        String content = "Hello World Hello Hello World";
        byte[] contentBytes = content.getBytes();//29

        List<Node> nodes = getNodes(contentBytes);

        System.out.println(nodes);

        System.out.println("=== 哈夫曼树 ===");
        Node huffmanTreeRoot = createHuffmanTree(nodes);
        huffmanTreeRoot.preOrder();

        
       byte[] huffmanCodeBytes = huffmanZip(contentBytes);
        System.out.println("压缩后的结果:"+Arrays.toString(huffmanCodeBytes));
        byte[] sourceBytes = decode(huffmanCodes, huffmanCodeBytes);
        String result = new String(sourceBytes);
        System.out.println("原来的字符串:"+result);

    }

在这里插入图片描述


文件的哈夫曼编码压缩、解压

上面完成的是字符串的压缩、解压,实际生活中大部分是对文件的压缩、解压

对文件的哈夫曼操作其实与字符串是一样的,只不过需要设置IO流读取文件,并且通过对象IO流写入、读取哈夫曼编码表

具体的方法代码:

/**
     * 将文件进行压缩
     * @param filePath 传入文件的路径
     * @param outPath 压缩文件的放置路径
     */

    public static void zipFile(String filePath,String outPath){

        //创建输出流
        FileOutputStream fileOutputStream = null;
        //创建文件的输入流
        FileInputStream fileInputStream = null;

        ObjectOutputStream objectOutputStream = null;

        try {
            //输入流
            fileInputStream = new FileInputStream(filePath);
            //创建和文件大小一样的byte[]
            byte[] b = new byte[fileInputStream.available()];
            //读取文件
            fileInputStream.read(b);

            //压缩原文件,获得文件对应的哈夫曼编码表
            byte[] huffmanBytes = huffmanZip(b);

            //输出流
            fileOutputStream = new FileOutputStream(outPath);

            //创建ObjectOutputStream对象流
            objectOutputStream = new ObjectOutputStream(fileOutputStream);

            //把哈夫曼编码后的字节数组希尔压缩文件
            objectOutputStream.writeObject(huffmanBytes);
            //写入哈夫曼编码表,用于解压
            objectOutputStream.writeObject(huffmanCodes);


        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                //关闭输入流
                fileInputStream.close();
                fileOutputStream.close();
                objectOutputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }


/**
     * 解压压缩文件
     * @param zipFile 压缩文件地址
     * @param outPath 解压文件要放的地址
     */

    public static void unZipFile(String zipFile,String outPath){
        //文件输入流
        InputStream is = null;
        //对象输入流
        ObjectInputStream ois = null;
        //文件输出流
        OutputStream os = null;

        try {
            //文件输入流
            is = new FileInputStream(zipFile);
            //is关联的对象输入流
            ois = new ObjectInputStream(is);

            //对象流读取 哈夫曼编码字符数组
             byte[] huffmanBytes = (byte[]) ois.readObject();
             //读取哈夫曼编码表
            Map<Byte,String> code = (Map<Byte, String>) ois.readObject();

            //解码,得到原始字符数组
            byte[] bytes = decode(code, huffmanBytes);

            //文件输出流
            os = new FileOutputStream(outPath);
            //写出数据
            os.write(bytes);

        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        finally {

            try {
                os.close();
                ois.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }


        }

    }

测试:

    public static void main(String[] args) {

        //测试压缩文件
        String filePath = "D:/point.jpg";
        String outPath = "D:/point.zip";
        String unzipOutPath = "D:/point2.jpg";

        zipFile(filePath,outPath);
        System.out.println("=== 压缩文件成功 ===");

        System.out.println("=== 解压文件  ===");
        unZipFile(outPath,unzipOutPath);

    }

图片是:来源http://www.polayoutu.com/u/qiushaneryu
在这里插入图片描述

进行了压缩824k->783k,无损解压

在这里插入图片描述


代码

在gitee上:完整代码,需要自取

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值