赫夫曼编码解压原理(接上篇)
- 解压原理解析
上篇已经分析了赫夫曼编码的压缩原理,相反解压就会清晰许多,无非是拿到压缩后的文件先转成字节数组,然后拿到对应的赫夫曼编码表,对照着表来解压还原字节数组,最后再输出到文件即可完成。 - 现在要考虑的问题是如何处理压缩后生成的byte数组和对应赫夫曼编码表,首先想到的是将字符数组中的每个字节转化成字符串形式的二进制,这点非常重要,它对应的是赫夫曼编码表里面的每个字符的编码,根据这个字符串形式的二进制,比对着赫夫曼编码表就能还原压缩前的数据。
- 下述方法就是如何将压缩后生成的字符数组根据二进制转成字符串。
/**
* byte转二进制字符串
* @param flag 判断是否不是最后一个byte 不是:true 是:false
* @param b 单个字节
* @return
*/
private static String byteToBitString(boolean flag,byte b) {
int temp = b;
//如果不是最后一个byte
//正数需要补高位 负数不需要但补也没影响
if(flag&&b>=0) {
temp |= 256;
}
String str = Integer.toBinaryString(temp);
//如果是最后一位且字节>=0 直接返回即可
if(!flag&&b>=0) {
return str;
}
//否则需要截取后八位返回
return str.substring(str.length()-8);
}
- 然后进行数据解压,需要传入压缩后的字节数组与对应赫夫曼编码表。
将字节数组转化成字符串形式的二进制之后,对应编码表还原数据的真实字节。
/**
* 数据解压
* @param bytes 压缩后的byte数组
* @param huffmanCodes 对应赫夫曼编码表
* @return
*/
public static byte[] huffmanDecod(byte[] bytes,Map<Byte,String> huffmanCodes) {
StringBuilder sb = new StringBuilder();
//遍历压缩后的byte[] 逐个将字节转成二进制字符串顺序添加到sb中
for (int i = 0; i < bytes.length; i++) {
sb.append(byteToBitString(i!=bytes.length-1,bytes[i]));
}
//这一步尤为重要 我们需要根据编码值反向找对应字节(与压缩恰好相反)
//所以需要反转Map的Key和Value,因为通过Key找Value方便,Value找Key效率极低。
Map<String,Byte> huffmanCodex = new HashMap<>();
for(Entry<Byte,String> entry : huffmanCodes.entrySet()) {
huffmanCodex.put(entry.getValue(), entry.getKey());
}
//定义集合listByte ,存储根据编码值还原后的byte
List<Byte> listByte = new ArrayList<>();
//定义标志量
int flag;
//遍历二进制字符串 这里需要注意 由于赫夫曼编码为不定长编码
//所以在比对过程中并不确定 每个字节对应的二进制字符串位数
//所以定义flag标志量,每次从最小一位开始尝试匹配 匹配结果 有且只有一种
//因为前面章节已经提到 谁也不是谁的前缀 也不是谁的后缀 所以存在且唯一
for (int i = 0; i < sb.length();) {
flag = 1;
while(true) {
//如果匹配到
if(huffmanCodex.get(sb.substring(i, i+flag))!=null) {
//就从二进制字符串sb中截取并get到对应字节存储到集合listByte中
listByte.add(huffmanCodex.get(sb.substring(i, i+flag)));
break;
}
//如果本轮没有匹配到 就+1继续匹配
else {
flag++;
}
}
//每匹配到一个字节,就把它对应的赫夫曼编码长度加到i上
//目的在于跳过已匹配的编码继续循环
i += flag;
}
//循环结束 所有还原后的字节都存储在了集合listByte中
byte[] by = new byte[listByte.size()];
//复制到byte数组中
for (int i = 0; i < by.length; i++) {
by[i] = listByte.get(i);
}
//返回
return by;
}
- 解压工作基本完成,接下来封装一层,因为实际应用中都是解压目标路径下的文件
/**
* 文件解压 将压缩包解压至目标路径
* @param srcFile 解压路径
* @param dstFile 压缩包路径
* @throws IOException
* @throws ClassNotFoundException
*/
public static void decodFile(String srcFile,String dstFile) throws IOException, ClassNotFoundException {
//读取
FileInputStream fis = new FileInputStream(dstFile);
//嵌套缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
//转对象流
ObjectInputStream ois = new ObjectInputStream(bis);
//读入
Object objBytes = ois.readObject();
//强转
byte[] bytesZip = (byte[])objBytes;
//读入
Object objHuffmanCodes = ois.readObject();
//抢转
Map<Byte,String> huffmanCodes = (Map<Byte,String>)objHuffmanCodes;
//关键一步 传入压缩后的字节数组与对应编码表解压
byte[] bytes = huffmanDecod(bytesZip,huffmanCodes);
FileOutputStream fos = new FileOutputStream(srcFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//解压后写出到文件
bos.write(bytes);
//资源关闭
ois.close();
bis.close();
bos.close();
}
- 测试
解压G盘Huffman目录下的一个zip
测试代码
//解压测试
@Test
public void test2() {
String srcFile = "G://Huffman/爪哇.zip";
String zipFile = "G://Huffman/Java.txt";
try {
decodFile(srcFile,zipFile);
System.out.println("解压成功");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
测试结果
大小对比
↓↓↓↓↓↓ 基于赫夫曼编码的数据解压部分完结,下面是前半部分压缩链接 ↓↓↓↓↓↓