化神期简介
化身期主要阐述的是解压的思路和解压的代码实现分析
提供完整代码,代码在筑基期中
博主空间
https://blog.youkuaiyun.com/JOElib?type=lately结丹期
https://blog.youkuaiyun.com/JOElib/article/details/123905069?spm=1001.2014.3001.5501
元婴期https://blog.youkuaiyun.com/JOElib/article/details/123913609?spm=1001.2014.3001.5501
目录
解压思想🐼
- 利用哈夫曼编码表,把对应的byte类型数据转换为二进制字符串
- 将二进制字符串拼接起来,形成一个哈夫曼编码字符串
- 将哈夫曼编码表对应的Map的key和value反转
- 根据新哈夫曼编码表将哈夫曼编码字符串解压成原来的内容(byte数组)
- 将byte数组转换为原来的字符串即可
代码实现与分析🐼
1.建立一个用于将byte变成哈夫曼的方法🐻
public static String byteToString(byte b,boolean flag) {
int temp = b;
temp |= 256;
String str = Integer.toBinaryString(temp);
if (flag) {
return str.substring(str.length() - 8);
}
return str.substring(str.length() - lastCount);
}
代码分析:🐨
- 参数列表:
- b代表的是压缩后的byte[]数组中的元素,即将该元素转化为哈夫曼编码
- flag为布尔标记,用于判断是否为最后一个元素
- 第一步,我们要利用自动类型转换,将b赋值给temp
- 将temp|=256(重点)
- 首先,temp自动类型转换的原因出现在这个地方,因为256对应的二进制是"1 0000 0000"共9位,如果我们直接用b(byte)类型,由于我们知道1byte = 8bit 导致这个语句无意义
- 其次,哈夫曼编码是以二进制补码的形式存储在计算机的,故哈夫曼编码就是补码
- 再者,当整形十进制数大于0时,调用toBinaryString()方法没有8位
- 假设,一个压缩后的正数10,调用该方法后变成"1010"
- 我们通过元婴期了解到,哈夫曼编码前面是每8位取一次,形成一个整形十进制数,如果不是最后一次取出的话,该哈夫曼编码长度一定为8
- 如果恰好是正数,通过3.1可知最终变成'1010",不满8位,即所取得不是我们想要的哈夫曼编码
- 后果:解压失败
- 通过tmep |= 256 能有效的保证正数一定有9位,保障了8位数据不丢失,并保障了数据的完整性,起补位的作用
- toBinaryString()方法
- 将整形十进制数转换为二进制补码即哈夫曼编码,注意,由于该方法是Integer里面的,所以,其补码获取的是32位的
- 所以后面涉及截断操作(即强制类型转换)
- 根据flag,判断是否为最后一个元素(false表示是最后一个元素)
- 如果不是,则截断后8位,即str.substring(str.length() - 8);
- 长度-8开始截断即后8位
- 如果是,则需要考虑需要截断多少位
- 因为最后一次取,不一定取到8位
- 借助最后一次所赋予的属性lastCount,就能明确知道多少位了,这就是元婴期中的坑在"6.创建一个压缩的方法🐻"中出现了lastCount = str.length记录了该长度
- 如果不是,则截断后8位,即str.substring(str.length() - 8);
- 通过上述方法就可以得到对应的无损哈夫曼编码字符串
2.创建一个解压的方法🐻
public static byte[] decode(byte[] huffmanCodesBytes,Map<Byte,String> huffmanCodes) {
var strBil = new StringBuilder();
for (int i = 0; i < huffmanCodesBytes.length; i++) {
boolean flag = (i != huffmanCodesBytes.length -1);
strBil.append(byteToString(huffmanCodesBytes[i],flag));
}
var map = new HashMap<String,Byte>();
for (var entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(),entry.getKey());
}
var list = new ArrayList<Byte>();
for (int i = 0; i < strBil.length();) {
Byte b ;
var count = 1;
while ((b = map.get(strBil.substring(i, i + count))) == null) {
count++;
}
list.add(b);
i += count;
}
var content = new byte[list.size()];
for (int i = 0; i < content.length; i++) {
content[i] =list.get(i);
}
return content;
}
代码分析:🐨
- 参数列表:
- byte[]为压缩后的byte[]数组,Map<Byte,String>为哈夫曼编码表
- new一个StringBuilder,用于字符串拼接作用,形成一个哈夫曼编码字符串
- 建立一个for循环,遍历byte数组里面的元素,将byte数组里面的每一个byte变成对应的哈夫曼编码,即调用上述的byteToString()方法
- 注意:如果到了最后一个元素,根据之前所述,最后一个元素的时候需要将flag改为false,即 i != huffmanCodesBytes.length -1
- 创建一个新的Map集合,用于存放新的哈夫曼编码表
- 由我们的知识可以知道,我们可以通过key从而得到对应的value
- 之前压缩的时候是通过ASCII获得对应的赫夫曼字符串,ASCII在key上,赫夫曼编码在value上
- 为了实现由赫夫曼编码字符串获得对应的ASCII,所以要将他们对调
- 利用增强for循环,在调用entrySet方法,将Map集合转换成Set集合,分别调用getKey()和getValue()方法,就能有效对调
- new一个结合List,用于存放ASCII码,原因是好管理
- 重点:由于我们想要从哈夫曼编码字符串中还原成对应的ASCII,所以我们要遍历该字符串
- 创建一个包装类Byte,用于存放取出来的ASCII 注意:不可以使用byte(基本数据类型)
- 创建一个计数器count,并初始化为1
- 创建一个while循环,里面的布尔表达式写(b = map.get(strBil.substring(i, i + count))) == null
- 解释:我们调用subString()方法,进行字符串截断,即截断[i,i+count)
- 如果我们获取的值为空,说明赫夫曼编码没有截全,所以要进入循环,使得count++,截取多一个,再进行获取,知道获取成功,说明截全了,退出循环
- 将获取的值加入集合中
- 再将i+=count,表示开始截取下一个哈夫曼编码(注意:这样写才可以保证不重叠读取)
- 通过不断的进行以上操作,就能把所有的元素都还原了
- 注意:更新表达式是空的,不能写任何东西
- 通过集合元素个数即list.size()创建对应的byte[]数组
- 通过循环将元素放回byte[]数组,最后返回byte[]数组,解压成功
3.封装代码🐻
public static String decode(byte[] huffmanBytes) {
return new String(decode(huffmanBytes,HUFFMAN_CODES));
}
代码分析:🐨
- 由于我们最终想要的是字符串,所以我们不妨来个方法重载,将返回值类型改为String
- 由于手动穿哈夫曼编码表太傻了,所以不写了
- 根据上述要求就写出了方法重载
结论🐼
相信看到这里的小伙伴都充满坎坷,如果四期全懂了,离我们压缩和解压文件就不远了,如果有不懂的地方和错误的地方,可以随时评论区留言,下一期也是最后一期,炼虚期!