赫夫曼压缩以及解压(最后一位不补码导致的问题修补)

最开始最后一位没有补码,导致当最后一位出现诸如001,00这样的字节时,没有这么多的位数,导致报错,因此对代码进行修改。

原先代码

import java.util.*;

public class ReviewHuffman {
    static Map<Byte,String> map=new HashMap<>();
    public static void main(String[] args) {
        String s="i like like like java do you like a java";
        byte[] origin=s.getBytes();
//        //将数组转换成List集合
        List<HuffmanNode> list = rankHuffman(origin);
//        //将数组装入List集合,并生成赫夫曼树
        HuffmanNode huffman = createHuffman(list);
//        //得到每个叶子节点的路径,即赫夫曼表
        Map<Byte, String> huffmanTable = getHuffmanTable(huffman);
//        //将赫夫曼表转化为字节数组
        byte[] huffmanBytes = getHuffmanBytes(origin, huffmanTable);
//        System.out.println(Arrays.toString(huffmanBytes));

        //封装好的赫夫曼压缩
//        byte[] bytes = huffmanZip(origin);
//        System.out.println(Arrays.toString(bytes));

        //赫夫曼压缩后解压
        byte[] bytes = deCodeHuffman(huffmanTable, huffmanBytes);
        System.out.println(new String(bytes));
    }
    /********************************************压缩*************************************************/
    //封装好了的赫夫曼压缩
    private static byte[] huffmanZip(byte[] origin){
        //将数组转换成List集合
        List<HuffmanNode> list = rankHuffman(origin);
        //将数组装入List集合,并生成赫夫曼树
        HuffmanNode huffman = createHuffman(list);
        //得到每个叶子节点的路径,即赫夫曼表
        Map<Byte, String> huffmanTable = getHuffmanTable(huffman);
        //将赫夫曼表转化为字节数组
        return getHuffmanBytes(origin, huffmanTable);
    }

    //先得到每个字符对应的出现次数
    private static List<HuffmanNode> rankHuffman(byte[] origin){
        Map<Byte,Integer> map=new HashMap<>();
        //封装每个字符出现的次数
        for (int i=0;i<origin.length;i++){
            if (map.get(origin[i])==null){
                map.put(origin[i],1);
            }else {
                map.put(origin[i],map.get(origin[i])+1);
            }
        }
        //存入List中
        List<HuffmanNode> list=new ArrayList<>();
        for (Map.Entry<Byte,Integer> tmp:map.entrySet()){
            list.add(new HuffmanNode(tmp.getKey(),tmp.getValue()));
        }
        return list;
    }
    //将List排序,并生成赫夫曼树
    private static HuffmanNode createHuffman(List<HuffmanNode> list){
        while (list.size()>1){
            Collections.sort(list);
            HuffmanNode leftNode=list.get(0);
            HuffmanNode rightNode=list.get(1);
            HuffmanNode parent=new HuffmanNode(null,leftNode.weight+rightNode.weight);
            //将得到的左右节点放在新的父节点下,并从原数组移除左右节点,放入父节点
            parent.left=leftNode;
            parent.right=rightNode;
            list.remove(leftNode);
            list.remove(rightNode);
            list.add(parent);
        }
        return list.get(0);
    }
    /**
     *
     * @param huffmanNode 生成的赫夫曼树的根节点
     * @param code 定义准则,左边为0 右边为1
     * @param path 每个叶子节点的路径
     * @return 返回map集合,里面装有所有叶子节点和它对应的路径
     */
    //得到赫夫曼表
    private static void getHuffmanTable(HuffmanNode huffmanNode,String code,StringBuilder path){
        StringBuilder builder=new StringBuilder(path);
        builder.append(code);
        if (huffmanNode!=null){
            //等于空说明为非叶子节点需要递归寻找
            if (huffmanNode.data==null){
                getHuffmanTable(huffmanNode.left,"0",builder);
                getHuffmanTable(huffmanNode.right,"1",builder);
            }else {//叶子节点,找到了一个了,需放入
                map.put(huffmanNode.data,builder.toString());
            }
        }
    }
    //重载赫夫曼树表
    private static Map<Byte,String> getHuffmanTable(HuffmanNode huffmanNode){
        StringBuilder strBu=new StringBuilder();
        getHuffmanTable(huffmanNode,"",strBu);
        return map;
    }
    //将赫夫曼表转化为字节数组
    private static byte[] getHuffmanBytes(byte[] origin,Map<Byte,String> map){
        byte[] tmp;
        StringBuilder stringBuilder=new StringBuilder();
        //先将赫夫曼表拼接
        for (int i=0;i<origin.length;i++){
            stringBuilder.append(map.get(origin[i]));
        }
        System.out.println(stringBuilder.toString());
        //算出字节总数
        int count=stringBuilder.length()/8;
        if (count==0){
            tmp=new byte[count];
        }else {
            tmp=new byte[count+1];
        }
        //将上面的字符stringBuilder拆分放进tmp中 每8个一组, (注意要将其转为2进制转换!!!)
        int index=0;//记录放了多少个字节了
        for (int i=0;i<stringBuilder.length();i+=8){
            if (i+8<stringBuilder.length()){
                tmp[index++]=(byte)Integer.parseInt(stringBuilder.substring(i,i+8),2);
            }else {
                tmp[index++]=(byte)Integer.parseInt(stringBuilder.substring(i),2);
            }
        }
        //以上完成后就将字符分割开,放进字节中了
        return tmp;
    }
    /**************************************************解压************************************************************/
    /**
     * 功能: 对赫夫曼压缩后的数据进行解压
     * @param originMap 数据和赫夫曼字符串的对应关系
     * @param huffZip 赫夫曼压缩后生成的数组
     * @return 返回原始(被压缩前的数组)
     */
    //对压缩的赫夫曼解码
    private static byte[] deCodeHuffman(Map<Byte,String> originMap,byte[] huffZip){
        //将原本的key value 转置为 value key
        Map<String,Byte> revMap=new HashMap<>();
        for (Map.Entry<Byte,String> t:originMap.entrySet()){
            revMap.put(t.getValue(),t.getKey());
        }
        StringBuilder all=new StringBuilder();
        //将赫夫曼数组转换成字符串拼接起来
        for (int i=0;i<huffZip.length;i++){
            //最后一次不用补位,是多少就是多少,其余的不足需要补高位
            all.append(bitToBitString(i != huffZip.length - 1,huffZip[i]));
        }
        //当下的字符串
        String tmp;
        //往后移动的距离
        int count;
        //从map中获取到的源字节
        Byte t;
        //源数组
        byte[] bytes;
        List<Byte> list=new ArrayList<>();
        for (int i=0;i<all.length();i=i+count){
            count=1;
            while (true){
                tmp=all.substring(i,i+count);
                t=revMap.get(tmp);
                if (t==null){//如果为空说明里面没有对应的哈夫曼表
                    count++;
                }else {
                    //不为空说明找到了,添加进去,跳出此次while循环
                    list.add(t);
                    break;
                }
            }
        }
        bytes=new byte[list.size()];
        for (int i=0;i< list.size();i++){
            bytes[i]=list.get(i);
        }
        return bytes;
    }
    /**
     * 功能:将二进制字节转成字符串
     * @param flag 标志高位是否需要补位,如果为true则需要补位,如果是false则不补位
     * @param huff 传入的byte
     * @return 传入的byte的二进制字符串 (补码形式返回)
     */
    private static String bitToBitString(boolean flag,byte huff){
        int tmp=huff;
        //flag为true进行补位,或上就行
         if (flag){
            tmp |=256;  //256 为 1_0000_0000 tmp与其或了之后,就能达到补位的效果
         }
        String str=Integer.toBinaryString(tmp);
        //因为是取的int的补码,所以我们只需要后面8位,前面8位截取掉
        if (str.length()>8) {
            return str.substring(str.length() - 8);
        }else {
            return str;
        }
    }
}
class HuffmanNode implements Comparable<HuffmanNode>{
    HuffmanNode left;
    HuffmanNode right;
    int weight;
    Byte data;

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

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

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

修改代码

修补思想:原先最后一位没有补码,现在对每一位都补位。这样的话到最后一位时,判断规则需做出改变,不能从前往后了,需从后往前,找到就直接退出循环。

                if (all.length()-i>8){
                    tmp=all.substring(i,i+count);
                }else {//判断最后一个了,从后往前,找出来了就直接退出
                    tmp=all.substring(all.length()-count);
                    flag=true;
                }

修改后压缩及解压文件代码:

package syf.huffmantree;

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

public class ReviewHuffman {
    static Map<Byte,String> map=new HashMap<>();
    public static void main(String[] args) {

//        String s="i like like like java do you like a java";
//        byte[] origin=s.getBytes();
//        //将数组转换成List集合
//        List<HuffmanNode> list = rankHuffman(origin);
//        //将数组装入List集合,并生成赫夫曼树
//        HuffmanNode huffman = createHuffman(list);
//        //得到每个叶子节点的路径,即赫夫曼表
//        Map<Byte, String> huffmanTable = getHuffmanTable(huffman);
//        //将赫夫曼表转化为字节数组
//        byte[] huffmanBytes = getHuffmanBytes(origin, huffmanTable);
//        System.out.println(Arrays.toString(huffmanBytes));

        /* 注释掉字符串压缩和解压
        //封装好的赫夫曼压缩,传入待压缩的文件的字节数组
        byte[] bytes = huffmanZip(origin);
        System.out.println(Arrays.toString(bytes));
        //赫夫曼压缩后的解压
        byte[] deByte = deCodeHuffman(map, bytes);
        System.out.println(new String(deByte));
        */

        //文件的压缩
//        String src="src/test/java/syf/TestHuffman/out682.txt";
//        String dest="src/test/java/syf/TestHuffman/out682.zip";
//        zipFile(src,dest);
//        System.out.println("压缩成功!!");
        //文件的解压
        String src="src/test/java/syf/TestHuffman/out682.zip";
        String dest="src/test/java/syf/TestHuffman/out6823.txt";
        unzipFile(src,dest);
        System.out.println("解压成功");
    }
    /********************************************压缩*************************************************/
    //封装好了的赫夫曼压缩
    private static byte[] huffmanZip(byte[] origin){
        //将数组转换成List集合
        List<HuffmanNode> list = rankHuffman(origin);
        //将数组装入List集合,并生成赫夫曼树
        HuffmanNode huffman = createHuffman(list);
        //得到每个叶子节点的路径,即赫夫曼表
        Map<Byte, String> huffmanTable = getHuffmanTable(huffman);
        //将赫夫曼表转化为字节数组
        return getHuffmanBytes(origin, huffmanTable);
    }

    //先得到每个字符对应的出现次数
    private static List<HuffmanNode> rankHuffman(byte[] origin){
        Map<Byte,Integer> map=new HashMap<>();
        //封装每个字符出现的次数
        for (int i=0;i<origin.length;i++){
            if (map.get(origin[i])==null){
                map.put(origin[i],1);
            }else {
                map.put(origin[i],map.get(origin[i])+1);
            }
        }
        //存入List中
        List<HuffmanNode> list=new ArrayList<>();
        for (Map.Entry<Byte,Integer> tmp:map.entrySet()){
            list.add(new HuffmanNode(tmp.getKey(),tmp.getValue()));
        }
        return list;
    }
    //将List排序,并生成赫夫曼树
    private static HuffmanNode createHuffman(List<HuffmanNode> list){
        while (list.size()>1){
            Collections.sort(list);
            HuffmanNode leftNode=list.get(0);
            HuffmanNode rightNode=list.get(1);
            HuffmanNode parent=new HuffmanNode(null,leftNode.weight+rightNode.weight);
            //将得到的左右节点放在新的父节点下,并从原数组移除左右节点,放入父节点
            parent.left=leftNode;
            parent.right=rightNode;
            list.remove(leftNode);
            list.remove(rightNode);
            list.add(parent);
        }
        return list.get(0);
    }
    /**
     * @param huffmanNode 生成的赫夫曼树的根节点
     * @param code 定义准则,左边为0 右边为1
     * @param path 每个叶子节点的路径
     * @return 返回map集合,里面装有所有叶子节点和它对应的路径
     */
    //得到赫夫曼表
    private static void getHuffmanTable(HuffmanNode huffmanNode,String code,StringBuilder path){
        StringBuilder builder=new StringBuilder(path);
        builder.append(code);
        if (huffmanNode!=null){
            //等于空说明为非叶子节点需要递归寻找
            if (huffmanNode.data==null){
                getHuffmanTable(huffmanNode.left,"0",builder);
                getHuffmanTable(huffmanNode.right,"1",builder);
            }else {//叶子节点,找到了一个了,需放入
                map.put(huffmanNode.data,builder.toString());
            }
        }
    }
    //重载赫夫曼树表
    private static Map<Byte,String> getHuffmanTable(HuffmanNode huffmanNode){
        StringBuilder strBu=new StringBuilder();
        getHuffmanTable(huffmanNode,"",strBu);
        return map;
    }
    //将赫夫曼表转化为字节数组
    private static byte[] getHuffmanBytes(byte[] origin,Map<Byte,String> map){
        byte[] tmp;
        StringBuilder stringBuilder=new StringBuilder();
        //先将赫夫曼表拼接
        for (int i=0;i<origin.length;i++){
            stringBuilder.append(map.get(origin[i]));
        }
//        System.out.println(stringBuilder.toString());
        //算出字节总数
        int count=stringBuilder.length()%8;//除出来等于0代表为8的倍数,否则加1
        if (count==0){
            tmp=new byte[stringBuilder.length()/8];
        }else {
            tmp=new byte[stringBuilder.length()/8+1];
        }
        //将上面的字符stringBuilder拆分放进tmp中 每8个一组, (注意要将其转为2进制转换!!!)
        int index=0;//记录放了多少个字节了
        for (int i=0;i<stringBuilder.length();i+=8){
            if (i+8>stringBuilder.length()){//不够8位
                tmp[index]=(byte)Integer.parseInt(stringBuilder.substring(i),2);
            }else {
                tmp[index]=(byte)Integer.parseInt(stringBuilder.substring(i,i+8),2);
            }
            index++;
        }
        //以上完成后就将字符分割开,放进字节中了
        return tmp;
    }
    /**************************************************解压************************************************************/
    /**
     * 功能: 对赫夫曼压缩后的数据进行解压
     * @param originMap 数据和赫夫曼字符串的对应关系
     * @param huffZip 赫夫曼压缩后生成的数组
     * @return 返回原始(被压缩前的数组)
     */
    //对压缩的赫夫曼解码
    private static byte[] deCodeHuffman(Map<Byte,String> originMap,byte[] huffZip){
        //将原本的key value 转置为 value key
        Map<String,Byte> revMap=new HashMap<>();
        for (Map.Entry<Byte,String> t:originMap.entrySet()){
            revMap.put(t.getValue(),t.getKey());
        }
        StringBuilder all=new StringBuilder();
        //将赫夫曼数组转换成字符串拼接起来
        for (int i=0;i<huffZip.length;i++){
            //最后一次不用补位,是多少就是多少,其余的不足需要补高位
            //最后一次需要补位,因为当最后一位为例如 000时,不补位只有一个0 不正确
            all.append(bitToBitString(huffZip[i]));
        }
        //当下的字符串
        String tmp;
        //往后移动的距离
        int count;
        //从map中获取到的源字节
        Byte t;
        //源数组
        byte[] bytes;
        List<Byte> list=new ArrayList<>();
        boolean flag=false;
        for (int i=0;i<all.length();){
            count=1;
            while (true){
                if (all.length()-i>8){
                    tmp=all.substring(i,i+count);
                }else {//判断最后一个了,从后往前,找出来了就直接退出
                    tmp=all.substring(all.length()-count);
                    flag=true;
                }
                t=revMap.get(tmp);
                if (t==null){//如果为空说明里面没有对应的哈夫曼表
                    count++;
                }else {
                    //不为空说明找到了,添加进去,跳出此次while循环
                    list.add(t);
                    break;
                }
            }
            i+=count;
            if (flag)//如果为true跳出,则说明为最后一个,且已经找到。直接跳出循环
                break;
        }
        bytes=new byte[list.size()];
        for (int i=0;i< list.size();i++){
            bytes[i]=list.get(i);
        }
        return bytes;
    }
    /**
     * 功能:将二进制字节转成字符串
//     * @param flag 标志高位是否需要补位,如果为true则需要补位,如果是false则不补位
     * @param huff 传入的byte
     * @return 传入的byte的二进制字符串 (补码形式返回)
     */
    private static String bitToBitString(byte huff){
        int tmp=huff;
        //flag为true进行补位,或上就行
        tmp |=256;  //256 为 1_0000_0000 tmp与其或了之后,就能达到补位的效果
        String str=Integer.toBinaryString(tmp);
        //因为是取的int的补码,所以我们只需要后面8位,前面8位截取掉
        return str.substring(str.length() - 8);
    }
    /******************************************文件压缩****************************************************/
    /**
     *
     * @param srcFile 要进行压缩的文件路径
     * @param destFile 压缩后文件放置的位置
     */
    private static void zipFile(String srcFile,String destFile){
        //创建缓冲流
        BufferedInputStream bi=null;
        BufferedOutputStream bo=null;
        //对象流,可按照写入顺序读出。一个为文件字节,一个为存储规则map
        ObjectOutputStream oos=null;
        //读取字节的数组
        byte[] getBytes;
        try {
            //将源文件的流读进来
            bi=new BufferedInputStream(new FileInputStream(srcFile));
            //获取源文件的字节大小,进行数组创建
            getBytes=new byte[bi.available()];
            //将文件读进getBytes
            bi.read(getBytes);
            //对文件进行压缩,得到赫夫曼压缩后数组
            byte[] huffmanBytes = huffmanZip(getBytes);
            //创建输出缓冲流
            bo=new BufferedOutputStream(new FileOutputStream(destFile));
            //使用对象流输出,方便按对象直接读取
            oos=new ObjectOutputStream(bo);
            oos.writeObject(huffmanBytes);
            oos.flush();
            oos.writeObject(map);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (oos!=null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bo!=null){
                try {
                    bo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bi!=null) {
                try {
                    bi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /******************************************文件解压****************************************************/
    /**
     *
     * @param srcFile 要进行解压的文件路径
     * @param destFile 解压后文件放置的位置
     */
    private static void unzipFile(String srcFile,String destFile){
        //创建缓冲流
        BufferedInputStream bi=null;
        BufferedOutputStream bo=null;
        //对象流,可按照写入顺序读出。一个为文件字节,一个为存储规则map
        ObjectInputStream ois=null;
        //读取字节的数组
        byte[] huffmanBytes;
        //压缩规则
        Map<Byte,String> zipRule;
        try {
            //将待解压的文件读进来
            bi=new BufferedInputStream(new FileInputStream(srcFile));
            //读成对象流,可以对象写出
            ois=new ObjectInputStream(bi);
            //写出压缩后的赫夫曼表,,按写入顺序读出
            huffmanBytes=(byte[]) ois.readObject();
            //写出压缩规则
            zipRule= (Map<Byte,String>)ois.readObject();
            //解压,得到原始字节
            byte[] bytes = deCodeHuffman(zipRule, huffmanBytes);
            //定义解压位置开始输出文件
            bo=new BufferedOutputStream(new FileOutputStream(destFile));
            bo.write(bytes);
            bo.flush();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois!=null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bo!=null){
                try {
                    bo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bi!=null) {
                try {
                    bi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class HuffmanNode implements Comparable<HuffmanNode>{
    HuffmanNode left;
    HuffmanNode right;
    int weight;
    Byte data;

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

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

    @Override
    public int compareTo(HuffmanNode o) {
        return this.weight-o.weight;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

syf_wfl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值