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.按步骤封装代码🐻
🐼结论
🐼压缩思路
- 获取字符串的byte数组
- 将byte数组里面的元素及其出现的个数记录在Map集合中
- 将Map集合元素放入List集合中,根据List集合创建对应的哈夫曼树
- 利用哈夫曼树创建对应的哈夫曼编码,并放入另一个Map集合中
- 根据哈夫曼编码表取出元素,并进行截断进行最后的总压缩
- 封装代码
字符串 -> 哈夫曼树 -> 哈夫曼编码 -> 压缩
🐼压缩的代码实现与分析
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; }}
代码分析:🐨
- Byte是byte的包装类,不能写byte,后面有妙用,对应的是ascii,存放字符的ascii码
- value是权,存放着某个字符串出现的个数
- 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;}
代码分析:🐨
- HUFFMAN_CODES这个常量是哈夫曼编码表,每个元素对应的哈夫曼编码都存放于里面,泛型为Byte(元素类型)和String(哈夫曼编码)
- STRING_BUFFER起辅助作用,用于辅助哈夫曼编码的完成
- 剩余属性都是解压所用,解压时再描述
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; }
代码分析:🐨
- 该方法的参数列表是byte[]数组,该byte[]数组是所需要压缩的字符串对应的byte[]数组,可以通过getBytes()这个方法获取
- new 一个Map集合,用于存放元素及其元素个数(相当于存放字符串里的字符及其字符个数),泛型分别指定为Byte(元素类型),Integer(元素个数)
- 注意:这里是遵循着变长编码的思路方式,不清楚可跳转至"筑基期"
- new一个List集合,用于创建哈夫曼树
- 注意:这里的原理如果不懂,可以跳转至"结丹期"
- 用增强for循环遍历byte[]数组
- 首先,我们先建立一个变量var来接受get()方法的返回值
- 如果get()方法返回值为null(Integer),说明该元素还没有被记录在Map集合中,所以调用put()方法,并指定个数为1
- 如果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); }
代码分析:🐨
- 注意:叶子节点才是我们存放数据的节点,所以创建新子树时,根节点的ascii赋值为null
- 其他略,不清楚请跳转"结丹期"
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()); } } }
代码分析:🐨
- 参数列表:
- node表示从哪一个节点开始,所以我们传入一个根节点即可
- code表示编码规则,我们有以下规则
- 如果向左子树走,我们就传"0"
- 如果向右子树走,我们就传"1"
- strBuf用于字符串拼接,从而获取某个元素最终的哈夫曼编码
- 首先:先new一个StringBuffer(cuStrBuf)
- 原因:如果我们不去new一个StringBuffer而直接往参数列表传过来的strBuf直接改动,会导致strBuf对应的字符串永远的在增长,没有回溯的过程,导致一个元素的哈夫曼编码串联在另一个元素上
- 字符串拼接,根据code拼接
- 如果当前节点不为空,说明还有机会去递归找到所需要的节点
- 如果当前节点的ascii为null,说明时非叶子节点,不是所需要的元素,进行递归编码
- 向左递归,赋"0",并将curStrBuf传过去
- 向有递归,赋"1",并将curStrBuf传过去
- 否则时叶子节点,是我们想要的元素,则调用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; }
代码分析:🐨
- 参数列表:
- 需要压缩的字符串对应的byte[]数组
- 哈夫曼编码表,用于压缩
- new一个StringBuilder,用于存放哈夫曼编码串
- 通过增强for循环,遍历里面的元素.根据哈夫曼编码表得出对应的哈夫曼编码并追加到哈夫曼编码串中
- 定义一个长度len(压缩后的数组的长度),并赋值为(strBuf.length() + 7) / 8
- 解释:压缩后的数组是byte[]数组,每个元素都是一个字节,每个字节有八个比特位
- 当最后一次不满八位的时候,我们也要存入数组中,所以我们要预留多一块空间
- +7相当于最后加了7位,保障了最后几位数组能够被存入数组中
- 创建对应的压缩后的数组以及临时字符串
- 创建对应的for循环,遍历哈夫曼编码串
- i = 0[从下标为0的元素开始];i < strBuf.length()[防止越界];i+=8[1byte = 8bit,每次取8位]
- 若i+8< strBuf.length()[,直接截断8位,即str = strBuf.substring(i,i+8);
- 否则说明已经到最后一个不足8位,若依然按照上述截断会发生异常,所以只需要 strBuf.substring(i),并将位数记录下来(解压时有巨大作用)
- 调用Integet.praseInt(str,2)方法,将二进制字符串转换为十进制数并存放如byte[]数组中,注意强制类型转换
- 将压缩后的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); }
🐼结论
压缩还是存在一定的难度,我们需要取理解其中的奥妙,解压过程中有更多的奥妙,敬请期待.