> 文档中心 > winRAR真难用,我决定自创一个(元婴期) 压缩

winRAR真难用,我决定自创一个(元婴期) 压缩


  元婴期简介

元婴期主要阐述的是压缩思路压缩的代码实现与分析

提供完整代码,代码在筑基期中     

博主空间https://blog.csdn.net/JOElib?spm=1011.2266.3001.5343筑基期https://blog.csdn.net/JOElib/article/details/123848806?spm=1001.2014.3001.5501结丹期https://blog.csdn.net/JOElib/article/details/123905069?spm=1001.2014.3001.5501


目录

🐼压缩思路

🐼压缩的代码实现与分析 

1.创建一个节点类🐻

代码分析:🐨

2.创建一个哈夫曼代码类🐻

代码分析:🐨

3.创建一个获取集合List的方法🐻

代码分析:🐨

4.建立一个创建哈夫曼树的方法🐻 

代码分析:🐨

5.创建一个获取哈夫曼编码的方法🐻 

代码分析:🐨 

 6.创建一个压缩的方法🐻

代码分析:🐨  

7.按步骤封装代码🐻 

🐼结论


 

🐼压缩思路

  1.  获取字符串的byte数组
  2.  将byte数组里面的元素及其出现的个数记录在Map集合中
  3.  将Map集合元素放入List集合中,根据List集合创建对应的哈夫曼树
  4.  利用哈夫曼树创建对应的哈夫曼编码,并放入另一个Map集合中
  5.  根据哈夫曼编码表取出元素,并进行截断进行最后的总压缩
  6.  封装代码

字符串 -> 哈夫曼树 -> 哈夫曼编码 -> 压缩 


🐼压缩的代码实现与分析 

1.创建一个节点类🐻

public class Node implements Comparable {    public Byte ascii;    public int value;    public Node left;    public Node right;    public Node(Byte ascii,int value) { this.ascii = ascii; this.value = value;    }    public String toString() { return "Node[ascii = " + ascii + " value = " + value + "]";    }    public int compareTo(Node node) { return value - node.value;    }}

代码分析:🐨

  1. Byte是byte的包装类,不能写byte,后面有妙用,对应的是ascii,存放字符的ascii码
  2. value是权,存放着某个字符串出现的个数 
  3. compareTo方法是为了排序创造哈夫曼树,toString是为了方便输出校验程序是否出错,构造方法是为了初始化

2.创建一个哈夫曼代码类🐻

public class HuffmanCode implements Serializable {    @Serial    private static final long serialVersionUID = 4420;    private static final HashMap HUFFMAN_CODES = new HashMap();    private static final StringBuffer STRING_BUFFER = new StringBuffer();    private static int lastCount;}

代码分析:🐨

  1. HUFFMAN_CODES这个常量是哈夫曼编码表,每个元素对应的哈夫曼编码都存放于里面,泛型为Byte(元素类型)和String(哈夫曼编码)
  2. STRING_BUFFER起辅助作用,用于辅助哈夫曼编码的完成
  3. 剩余属性都是解压所用,解压时再描述 

3.创建一个获取集合List的方法🐻

public static List getNodes(byte[] content) { var map = new HashMap(); var list = new ArrayList(); for (var item : content) {     var count = map.get(item);     if (count == null) {  map.put(item,1);     }else {  map.put(item,++count);     } }    for (var entry : map.entrySet()) {     list.add(new Node(entry.getKey(),entry.getValue())); } return list;    }

代码分析:🐨

  1. 该方法的参数列表是byte[]数组,该byte[]数组是所需要压缩的字符串对应的byte[]数组,可以通过getBytes()这个方法获取
  2. new 一个Map集合,用于存放元素及其元素个数(相当于存放字符串里的字符及其字符个数),泛型分别指定为Byte(元素类型),Integer(元素个数)
    1. 注意:这里是遵循着变长编码的思路方式,不清楚可跳转至"筑基期"
  3. new一个List集合,用于创建哈夫曼树
    1. 注意:这里的原理如果不懂,可以跳转至"结丹期"
  4. 用增强for循环遍历byte[]数组
    1. 首先,我们先建立一个变量var来接受get()方法的返回值
    2. 如果get()方法返回值为null(Integer),说明该元素还没有被记录在Map集合中,所以调用put()方法,并指定个数为1
    3. 如果get()方法返回值不为null,说明该元素已经在Map集合中记录了,所以调用put方法,并指定个数为++count,表示到此刻为止,一共有++count个这样的元素

4.建立一个创建哈夫曼树的方法🐻 

public static Node createHuffmanTree(List list) { while (list.size() > 1) {     Collections.sort(list);     var leftNode = list.get(0);     var rightNode = list.get(1);     var parentNode = new Node(null, leftNode.value + rightNode.value);     parentNode.left = leftNode;     parentNode.right = rightNode;     list.add(parentNode);     list.remove(1);     list.remove(0); } return list.get(0);    }

代码分析:🐨

  1. 注意:叶子节点才是我们存放数据的节点,所以创建新子树时,根节点的ascii赋值为null
  2. 其他略,不清楚请跳转"结丹期"

5.创建一个获取哈夫曼编码的方法🐻 

public static void getCodes(Node node,String code,StringBuffer strBuf) { var curStrBuf = new StringBuffer(strBuf); curStrBuf.append(code); if (node != null) {     if (node.ascii == null) {  getCodes(node.left,"0",curStrBuf);  getCodes(node.right,"1",curStrBuf);     }else {  HUFFMAN_CODES.put(node.ascii,curStrBuf.toString());     } }    }

代码分析:🐨 

  1. 参数列表:
    1. node表示从哪一个节点开始,所以我们传入一个根节点即可
    2. code表示编码规则,我们有以下规则
      1. 如果向左子树走,我们就传"0"
      2. 如果向右子树走,我们就传"1"
    3. strBuf用于字符串拼接,从而获取某个元素最终的哈夫曼编码
  2. 首先:先new一个StringBuffer(cuStrBuf)
    1. 原因:如果我们不去new一个StringBuffer而直接往参数列表传过来的strBuf直接改动,会导致strBuf对应的字符串永远的在增长,没有回溯的过程,导致一个元素的哈夫曼编码串联在另一个元素上
  3. 字符串拼接,根据code拼接
  4. 如果当前节点不为空,说明还有机会去递归找到所需要的节点
    1. 如果当前节点的ascii为null,说明时非叶子节点,不是所需要的元素,进行递归编码
    2. 向左递归,赋"0",并将curStrBuf传过去
    3. 向有递归,赋"1",并将curStrBuf传过去
  5. 否则时叶子节点,是我们想要的元素,则调用HUFFMAN_CODES.put()方法,将元素类型及其哈夫曼编码加进去

针对解释2的图解 

 6.创建一个压缩的方法🐻

public static byte[] zip(byte[] content,Map huffmanCodes) { var strBuf = new StringBuilder(); for (var item : content) {     strBuf.append(huffmanCodes.get(item)); } var len = (strBuf.length() + 7) / 8; var huffmanCodesBytes = new byte[len]; String str; var index = 0; for (int i = 0; i < strBuf.length(); i+= 8) {     if (i + 8 < strBuf.length()) {  str = strBuf.substring(i,i+8);     }else {  str = strBuf.substring(i);  lastCount = str.length();     }     huffmanCodesBytes[index++] = (byte) Integer.parseInt(str,2); } return huffmanCodesBytes;    }

代码分析:🐨  

  1.  参数列表:
    1. 需要压缩的字符串对应的byte[]数组
    2. 哈夫曼编码表,用于压缩
  2. new一个StringBuilder,用于存放哈夫曼编码串
  3. 通过增强for循环,遍历里面的元素.根据哈夫曼编码表得出对应的哈夫曼编码并追加到哈夫曼编码串中
  4. 定义一个长度len(压缩后的数组的长度),并赋值为(strBuf.length() + 7) / 8
    1. 解释:压缩后的数组是byte[]数组,每个元素都是一个字节,每个字节有八个比特位
    2. 当最后一次不满八位的时候,我们也要存入数组中,所以我们要预留多一块空间
    3. +7相当于最后加了7位,保障了最后几位数组能够被存入数组中
  5. 创建对应的压缩后的数组以及临时字符串
  6. 创建对应的for循环,遍历哈夫曼编码串
    1. i = 0[从下标为0的元素开始];i < strBuf.length()[防止越界];i+=8[1byte = 8bit,每次取8位]
    2. 若i+8< strBuf.length()[,直接截断8位,即str = strBuf.substring(i,i+8);
    3. 否则说明已经到最后一个不足8位,若依然按照上述截断会发生异常,所以只需要 strBuf.substring(i),并将位数记录下来(解压时有巨大作用)
    4. 调用Integet.praseInt(str,2)方法,将二进制字符串转换为十进制数并存放如byte[]数组中,注意强制类型转换
  7. 将压缩后的byte[]数组返回即可

7.按步骤封装代码🐻 

public static byte[] huffmanCodeZip(String s) { var content = s.getBytes(); var list = getNodes(content); var huffmanTreeRoot = createHuffmanTree(list); getCodes(huffmanTreeRoot,"",STRING_BUFFER); return zip(content,HUFFMAN_CODES);    }

🐼结论

        压缩还是存在一定的难度,我们需要取理解其中的奥妙,解压过程中有更多的奥妙,敬请期待. 

央视天气网