java实现赫夫曼编码实现文件的压缩与解压缩

java实现赫夫曼编码实现文件的压缩与解压缩

一、基本概念

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
  赫夫曼码的码字(各符号的代码)是异前置码字,即任一码字不会是另一码字的前面部分,这使各码字可以连在一起传送,中间不需另加隔离符号,只要传送时不出错,收端仍可分离各个码字,不致混淆。

二、思路分析

哈夫曼编码就是利用哈夫曼树来实现的,通过构造哈夫曼树,一半在构造过程中,向左路径规定为0,向右路径规定为1,这样元素路径上的0和1就构成了哈夫曼编码。下面看一个例子,我们以在哈夫曼树中提到的序列为例:13,7,8,3,29,6,1,构造哈夫曼树如下:

如上图,我们可以看到已经给左右分支都编上了0/1,这样29的编码就是1,7的编码就是100,8的编码就是101,1的编码就是11000,剩下的以此类推。
利用哈夫曼编码的特征,我们可以使用哈夫曼编码进行压缩文件,以一个简单的字符串为例:“i like like like java do you like a java”,如果直接使用二进制进行编码,则编码长度为359,下面我们来看使用哈夫曼编码的情况。我们首先统计各个字符出现的次数来作为权值,构造一棵哈夫曼树,其中:d:1 y:1 u:1 j:1 v:2 o:2 l:2 k:4 e:4 i:5 a:5 空格:9。

然后使用上述方法进行编码:
o:1000 u:10010 d:100110 y:100111 i:101 a:110 k:1110 e:1111 j:0000 v:0001 l:001 空格:01
所以整个字符串的编码就是:1010100110111101111010011011110111101001101111011110100001100001110011001101000011001111000100100100110111101111011100100001100001110
长度:133,压缩率(359-133)/359=62.9%。
注意:在构造哈夫曼树时,由于哈夫曼树的结构可能不同,所以编码也可能不同,但是wpl是一样的,都是最小的。最后生成的哈夫曼编码长度也是一样的。

三、代码实现

利用哈夫曼编码进行压缩分为以下几步:

将待压缩文件存入byte[]数组中
将byte[]数组中的内容进行统计,并返回节点所组成的list
创建哈夫曼树
利用哈夫曼树进行哈夫曼编码并存放起来
根据哈夫曼编码得到的编码表,将进行哈夫曼编码后的0/1形式的串按8位划分并转化为十进制形式进行存储
解压操作是压缩操作的逆操作:

首先接收压缩后的以十进制存放的byte[]数组以及编码表
将编码后的十进制数还原为二进制数,即0/1形式
根据编码表,将二进制形式串还原为初始形式。

四、详细代码

构建结点类

package com.ma.tree.huffman;

public class NodeCode implements Comparable<NodeCode>{
    //用来描述文字(字母)的十进制数
    public Byte data;
    //权重
    public int weight;

    public NodeCode left;

    public NodeCode right;

    public NodeCode(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    public NodeCode(int weight) {
        this.weight = weight;
    }

    @Override
    public int compareTo(NodeCode o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "NodeCode{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }

    //前序遍历
    public void preselect(){
        System.out.println(this);
        if (this.left != null){
            this.left.preselect();
        }
        if (this.right != null){
            this.right.preselect();
        }
    }
}

压缩与解压

package com.ma.tree.huffman;

import java.nio.charset.StandardCharsets;
import java.sql.SQLOutput;
import java.util.*;

public class HuffmanCodes {

    /**
     * 1.需要统计字母所出现的次数
     * 2.创建赫夫曼树
     * 3.获取赫夫曼编码
     * 4.数据压缩
     */

    private static Map<Byte,String> map = new HashMap<>();

    private static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) {

        String string = "I'm mawencheng what's your name";

        byte[] bytes = string.getBytes();
        System.out.println(Arrays.toString(bytes));

        List<NodeCode> nodeWeight = getNodeWeight(bytes);

        NodeCode root = createHuffmanTree(nodeWeight);

        Map<Byte,String> huffmanCodes = createHuffmanCodes(root);
        System.out.println("生成的赫夫曼编码:"+huffmanCodes);

        byte[] zip = zip(bytes,huffmanCodes);
        System.out.println("压缩后的数组:"+Arrays.toString(zip));

        float p = ( (float)bytes.length - (float)zip.length ) / (float) bytes.length;
        System.out.println("压缩比例:"+p);

        byte[] decode = decode(map,zip);
        System.out.println("解压后:"+new String(decode));
    }


    /*
    * 统计字符串所出现的次数
    * 返回集合[Node{data=32,weight=4},]
    * */
    public static List<NodeCode> getNodeWeight(byte[] bytes){

        List<NodeCode> nodeCodes = new ArrayList<>();
        //每一个byte出现的次数,使用一个map集合,  key=byte  value=weight
        Map<Byte,Integer> map = new HashMap<>();

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

        //把每一个map里的对象转NodeCode对象,放在集合里
        for (Map.Entry<Byte,Integer> entry : map.entrySet()){
            nodeCodes.add(new NodeCode(entry.getKey(),entry.getValue()));
        }

        return nodeCodes;
    }


    //创建赫夫曼树
    public static NodeCode createHuffmanTree(List<NodeCode> nodeCodes){
        //循环处理
        while (nodeCodes.size() > 1){
            //排序,从小到大
            Collections.sort(nodeCodes);
            //找到最小的两个元素(结点)
            NodeCode left = nodeCodes.get(0);
            NodeCode right = nodeCodes.get(1);
            //两个最小结点weight相加,生成新的结点
            NodeCode root = new NodeCode(left.weight+right.weight);
            //将新的结点作为父节点,构成一个新的二叉树
            root.right = right;
            root.left = left;
            //移除最小的两个元素
            nodeCodes.remove(left);
            nodeCodes.remove(right);
            //将新结点添加进集合
            nodeCodes.add(root);
        }
        return nodeCodes.get(0);
    }


    /*获取赫夫曼编码,
     * 1.往左取0 往右取1
     * eg:
     * a:101 b:1001
     * 完整赫夫曼编码为: 1011001
     * StringBuilder codes  用来拼接赫夫曼编码
     * Map 用来存储叶子结点路径
     *
     * */
    public static void getHuffmanCode(NodeCode nodeCode,String code,StringBuilder stringBuilder){
        StringBuilder newString = new StringBuilder(stringBuilder);
        newString.append(code);
        if (nodeCode != null){
            if (nodeCode.data == null){
                //向左递归
                getHuffmanCode(nodeCode.left,"0",newString);
                //向右递归
                getHuffmanCode(nodeCode.right,"1",newString);
            }else {
                map.put(nodeCode.data,newString.toString());
            }
        }
    }


    //设置统一入口
    public static Map<Byte,String> createHuffmanCodes(NodeCode root){
        if (root == null){
            return null;
        }
        //处理左子树
        getHuffmanCode(root.left,"0",stringBuilder);
        //处理右子树
        getHuffmanCode(root.right,"1",stringBuilder);
        return map;
    }


    //数据压缩
    public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        //用来拼接所有叶子结点路径
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : bytes){
            stringBuilder.append(huffmanCodes.get(b));

        }
        int length;
        if (stringBuilder.length() % 8 ==0){
            length = stringBuilder.length() / 8;
        }else {
            length = stringBuilder.length() / 8 + 1;
        }

        //压缩数据的数组
        byte[] huffmanBytes = new byte[length];

        int index = 0;
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String str;
            if ( i+8 > stringBuilder.length()){
                str = stringBuilder.substring(i);
            }else {
                str = stringBuilder.substring(i,i+8);
            }

            huffmanBytes[index] = (byte) Integer.parseInt(str,2);

            index++;
        }
        return huffmanBytes;
    }


    //如何对数据进行解压
    public static String byteToString(boolean isRepair,byte b){
        /*
        * byte:占8个二进制位
        * int:占32个二进制位
        * 正数时需要补高8位,负数不需要补
        * */
        int temp = b;
        if (isRepair){
            temp = temp | 256;
        }
        String string= Integer.toBinaryString(temp);
        if (isRepair){
            return string.substring(string.length()-8);
        }else {
            return string;
        }
    }


    public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < huffmanBytes.length;i++) {
            byte b = huffmanBytes[i];

            boolean flag = false;
            if (i == huffmanBytes.length-1 ){
                flag = true;
            }
            stringBuilder.append(byteToString(!flag,b));

        }

        Map<String,Byte> map = new HashMap<>();
        for (Map.Entry<Byte,String> entry : huffmanCode.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }

        List<Byte> bytes  = 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;
                }
            }
            bytes.add(b);
            i += count;
        }

        byte[] buff = new byte[bytes.size()];
        for (int i = 0; i < buff.length; i++) {
            buff[i] = bytes.get(i);
            System.out.println(buff[i]);
        }

        return buff;
    }
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值