元婴期简介
元婴期主要阐述的是压缩思路和压缩的代码实现与分析
提供完整代码,代码在筑基期中
博主空间
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
目录
🐼压缩思路
- 获取字符串的byte数组
- 将byte数组里面的元素及其出现的个数记录在Map集合中
- 将Map集合元素放入List集合中,根据List集合创建对应的哈夫曼树
- 利用哈夫曼树创建对应的哈夫曼编码,并放入另一个Map集合中
- 根据哈夫曼编码表取出元素,并进行截断进行最后的总压缩
- 封装代码
字符串 -> 哈夫曼树 -> 哈夫曼编码 -> 压缩
🐼压缩的代码实现与分析
1.创建一个节点类🐻
public class Node implements Comparable<Node> {
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<Byte,String> 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<Node> getNodes(byte[] content) {
var map = new HashMap<Byte,Integer>();
var list = new ArrayList<Node>();
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<Node> 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<Byte,String> 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);
}
🐼结论
压缩还是存在一定的难度,我们需要取理解其中的奥妙,解压过程中有更多的奥妙,敬请期待.