赫夫曼编码 压缩与解压

假如有这样如下字符串(40个字符 包括空格),需要存储到文件中。

i like like like java do you like a java

对于字符串来说,它是由字符类型组装起来的,字符类型即char类型,熟悉java的都知道java中char是基本的数据类型之一,占据一个字节。对于该字符串的二进制表示如下

1101001 100000 1101100 1101001 1101011 1100101 100000 1101100 
1101001 1101011 1100101 100000 1101100 1101001 1101011 1100101 
100000 1101010 1100001 1110110 1100001 100000 1100100 1101111 
100000 1111001 1101111 1110101 100000 1101100 1101001 1101011 
1100101 100000 1100001 100000 1101010 1100001 1110110 1100001 

对于每个字符使用一个字节来存储着实有些浪费,因此有了赫夫曼编码,通过使用赫夫曼编码来规定字符串中的单个字符需要多少个二进制来存储。
举个例子
对于空格来说 我们使用的二进制是10000000如果我们可以使用10来存储空格的话,压缩的效率不是很高吗,当然使用什么二进制的串来表示空格,需要由字符串生成的赫夫曼编码来决定。

1. 赫夫曼编码

思路:

  1. 首先构造结点存储字符出现的次数,以及字符
class NodeDemo implements Comparable<NodeDemo>{
	  byte data;//数据
	  int weight;//次数
	  NodeDemo left;//左节点
	  NodeDemo right;//右节点
	
	  public NodeDemo(byte data, int weight) {
	      this.data = data;
	      this.weight = weight;
	  }
	
	  public NodeDemo(int weight) {
	      this.weight = weight;
	  }
	
	  @Override
	  public String toString() {
	      return "NodeDemo{" +
	              "data=" + data +
	              ", weight=" + weight +
	              '}';
	  }
	
	  @Override
	  public int compareTo(NodeDemo o) {
	      return this.weight-o.weight;
	  }
	
	  //前序遍历
	  public void preOrder(){
	      System.out.println(this.toString());
	      if(this.left!=null){
	          this.left.preOrder();
	      }
	      if(this.right!=null){
	          this.right.preOrder();
	      }
	  }
}
  1. 将字符串转换为对应字符数组,根据字符数组中字符出现的次数,以及字符的值,存储到list列表中。
 public static List<NodeDemo> getNodes(String data){
        byte[] arr = data.getBytes();//转换成字节数据
        Map<Byte,Integer> map = new HashMap<>();
        List<NodeDemo> list = new ArrayList<>();
        for(byte b: arr){
            Integer count = map.get(b);
            if(count==null){
                map.put(b,1);
            }else{
                map.put(b,count+1);
            }
        }
        for(Map.Entry<Byte,Integer> entry : map.entrySet()){
            list.add(new NodeDemo(entry.getKey(),entry.getValue()));
        }
        return list;
    }
  1. 根据list列表按照构建赫夫曼树。
  //构建huffmanTree
    public static NodeDemo createHuffmanTree(List<NodeDemo> node){
        while(node.size()>1){
            Collections.sort(node);//排序
            NodeDemo left = node.get(0);
            NodeDemo right = node.get(1);
            NodeDemo parentNode = new NodeDemo(left.weight+right.weight);
            parentNode.left=left;
            parentNode.right=right;
            node.remove(left);
            node.remove(right);
            node.add(parentNode);
        }
        return node.get(0);//返回头结点  就是 根结点。
    }
  1. 根据赫夫曼树生成赫夫曼编码
 static StringBuilder stringBuilder = new StringBuilder();
 static Map<Byte,String> huffmanCodes = new HashMap<>();//存储huffman编码

 //根据hfm树生成hfm编码
 public static void getCodes(NodeDemo node,String code,StringBuilder stringBuilder){
     //构建新的StringBuilder stringBuilder
     StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
     //存储之前存储的结点 上面的代码非常的精妙。
     stringBuilder1.append(code);
     if(node.data==0){//说明是非叶子结点
         getCodes(node.left,"0",stringBuilder1);//左孩子存储0
         getCodes(node.right,"1",stringBuilder1);//右孩子存储1
     }else{//叶子结点
         huffmanCodes.put(node.data,stringBuilder1.toString());
     }
 }
2. 压缩文件

思路: huffman编码得出之后 根据得出的huffman编码进行重新存储字符串

/**
@parm bytes是要压缩的字符串  huffmanCode是压缩的规则 即规定用多长的二进制来存储
*/
public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
        //进行压缩
        StringBuilder str = new StringBuilder();
        for(byte b:bytes){//遍历字符串的字符数据  比如a
            str.append(huffmanCodes.get(b)); //获取a 对应的huffmanCode 进行字符串的连接
        }
        System.out.println(str.toString());
        //但是删除拼接起来的是字符串,而非二进制 仅此要对二进制文件每8为进行存储为字节
        int len;
        if(str.length()%8==0){
            len=str.length()/8;
        }else{
            len = str.length()/8+1;
        }
        //创建存储压缩后的数组
        byte[] bytes1 = new byte[len];
        int index=0;
        for(int i=0;i<str.length();i+=8){
            String  strBytes =null;
            if(i+8>str.length()){//对于最后一位可能不被8整数的情况
                strBytes = str.substring(i);
            }else{
                strBytes = str.substring(i,i+8);
            }
            bytes1[index]=(byte)Integer.parseInt(strBytes,2);//后面指定进制
            index++;
        }
        return bytes1;
    }

在这里插入图片描述
我们计算下压缩率
原字符串的长度是40个字节,压缩后的字符串是17
压缩率 (40-17)/40=57.5%

赫夫曼解码

思路:

  1. 对新形成的字符数据转化为二进制串
  2. 根据二进制串和赫夫曼编码解码为对应的字符串。
 //根据生成的二进制数组转换成二进制字符串
    public static byte[] decode(byte[] bytes){
        //如果是最后一个字符串的话 不进行补高位
        //解释 : 因为新形成的字符串 并不是按照1个字节八位进行存储的。因此最后一个未被8整数的二进制字符串 不能进行高位补0
        StringBuilder stringBuilder = new StringBuilder();
        for(int i=0;i<bytes.length;i++){//遍历数组
            if(i==bytes.length-1){
                stringBuilder.append(toBinary(false,bytes[i]));
            }else{
                stringBuilder.append(toBinary(true,bytes[i]));
            }
        }
        Map<String,Byte> map = new HashMap<>();
        //反转huffmanCode
        for(Map.Entry<Byte,String> entry:huffmanCodes.entrySet()){
            map.put(entry.getValue(),entry.getKey());
        }
        //前面我们根据a->1000 来进行压缩转化
        //解压时 我们需要根据 1000  来推出 a
        List<Byte> list = new ArrayList<>();
        //for循环遍历进行匹配

        for(int i=0;i<stringBuilder.length();){
            int count =1;
            Byte temp=null;
            boolean flag = true;
            while(flag){
            	// 10101000101111111100
                String b = stringBuilder.substring(i,i+count);
                // i不动 count 向后移动 进行截取
                //查找是否有对应的集合
                temp = map.get(b);
                if(temp!=null){//说明找到
                    flag=false;
                }else{
                    count++;
                }
            }
            i+=count;
            list.add(temp);//添加到list集合中
        }
        byte[] bytes1 = new byte[list.size()];
        for(int i =0;i<list.size();i++){
            bytes1[i]=list.get(i);
        }
        return bytes1;
    }

    /**
     *
     * @param flag 用来判断是否是最后一位
     * @param b 要处理的数字
     */
    public static  String toBinary(boolean flag,byte b){

        int temp = b;//将b转换为int
        if(flag){//如果是正数 补高位 1 0000 0000 和 b  及逆行按位异或
            temp |= 256;
         // 0000 0000 0000 0110
        //  0000 0001 0000 0000
        //进行异或处理的时候  最后取八位就是对应需要的字符
        }
        String str = Integer.toBinaryString(temp);
        if(flag){
            return str.substring(str.length()-8);
        }else{
            return str;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值