huffman编码压缩与解压
学习huffman编码后,了解了网络上主流的编码压缩和解码方式。但解压代码中存在一些bug,在这里和大家分享一下解决方法。
bug如下:
解压时,最后一个字节转成二进制时,位数的不确定性可能导致解压后字符串错误。比如,byte 存储为1,原先的编码后的字符串最后几位可能为 001 或者 0001。使用Integer中的字节转换二进制,解析byte1,正数只会返回1,此时如果不做处理,解码后的字符串可能会少0,可能报异常或者解压错误
解决方法:
- 思路:单独处理最后一个byte,还是按照原先的方式先转化成二进制字符串,然后与原先编码获得的字符串比较长度,如果相等,那就直接拼接,如果不相等,那就先末端补0,同时比较,直到解压后的字符串长度和编码后的字符串长度一致。
- 处理bug部分代码如下:
// 首先转化成二进制符号,然后拼接成字符串-注意末尾byte的处理
StringBuilder str = new StringBuilder();
boolean flag;
byte b;
for (int i = 0; i < huffmanBytes.length - 1; i++) {
// 判断是否被8整除,如果整除就不用判断最后一个数据,全部需要补位截取
b = huffmanBytes[i];
str.append(byteToBinary(true, b));
}
// 单独处理一下最后一位byte
b = huffmanBytes[huffmanBytes.length - 1];
String lastByteStr = byteToBinary(false, b);
// 如果长度相等,那正好。拼接上去
if (str.length() + lastByteStr.length() == huffmanCodesStr.length()) {
str.append(lastByteStr);
} else {
// 如果长度不够,那就先补0,直到总长度相等,再拼接
while (str.length() + lastByteStr.length() < huffmanCodesStr.length()) {
str.append(0);
}
str.append(lastByteStr);
}
完整代码供大家参考:
解压及byte转化成二进制代码如下:
// 解压
public static byte[] decode(byte[] huffmanBytes, Map<Byte, String> pathCodes) {
// 首先转化成二进制符号,然后拼接成字符串-注意末尾byte的处理
StringBuilder str = new StringBuilder();
boolean flag;
byte b;
for (int i = 0; i < huffmanBytes.length - 1; i++) {
// 判断是否被8整除,如果整除就不用判断最后一个数据,全部需要补位截取
b = huffmanBytes[i];
str.append(byteToBinary(true, b));
}
// 单独处理一下最后一位byte
b = huffmanBytes[huffmanBytes.length - 1];
String lastByteStr = byteToBinary(false, b);
// 如果长度相等,那正好。拼接上去
if (str.length() + lastByteStr.length() == huffmanCodesStr.length()) {
str.append(lastByteStr);
} else {
// 如果长度不够,那就先补0,直到总长度相等,再拼接
while (str.length() + lastByteStr.length() < huffmanCodesStr.length()) {
str.append(0);
}
str.append(lastByteStr);
}
System.out.println("解码后的哈夫曼编码字符串为:" + str.toString() + "长度为:"+ str.length());
// 然后对应原先的map表 i=100 反转 制定新的map表 100 =i
Map<String, Byte> map = new HashMap<String, Byte>();
for (Map.Entry<Byte, String> entry : pathCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 扫描上面的code字符串,对应出单词空格保存到list中
List<Byte> list = new ArrayList<>();
for (int j = 0; j < str.length();) {
// i只是前进所用,真正扫描寻找到有为止,还需要一个索引
int count = 1;
// 遍历查找的结束标识
boolean flag2 = true;
String subStr = null;
// 注意越界判断
while (flag2 && (j + count) <= str.length()) {
// 截取子串,进行map中key值比较
subStr = str.substring(j, j + count);
// b2 = map.get(subStr);
if (map.containsKey(subStr)) {
flag2 = false;
} else {
// 没有的话继续遍历后移
count++;
}
}
// 将这个byte添加到list中
list.add(map.get(subStr));
// 查找到一个后,后移到下一个j起始处.此处后移了j那么for中就不要再后移了
j += count;
}
// 扫描完所有的字符串,将list中的数据转化成byte[]
byte[] bt = new byte[list.size()];
for (int i = 0; i < bt.length; i++) {
bt[i] = list.get(i);
}
return bt;
}
// 首先需要一个将byte转化为电脑储存的补码二进制形式的方法
/**
* @param flag
* 传进来的字符串中是否末尾的标识 末尾不一定有8位长,直接补齐8位可能出错。所以不在这里补齐 比如 0100 00100
* 都是4 但是我们返回100即可,补齐的问题交给解压方法
* @param b
* 需要转化的字节数
* @return
*/
public static String byteToBinary(boolean flag, byte b) {
// Integer中有直接转化成二进制的方法
// 转化类型
int num = b;
// 转化
// 正数转化呢,只有后几位,---所以需要补位0 补齐8 位
// 256 1 0000 0000 与256或计算,就可以得到后八位长度 负数不影响,只要后8位
if (flag) {
num |= 256;
}
String binaryStr = Integer.toBinaryString(num);
if (flag || num < 0) {
// true 就是还没到末尾 都是8位 以及负数的情况
// 但是注意!负数转化完是int类型32位的二进制补码--所以需要取出后8位
return binaryStr.substring(binaryStr.length() - 8);
} else {
// false 到了末尾值,正数,不足8位,直接返回该有的长度就可以。
return binaryStr;
}
}
完整的整个编码压缩过程请到这里:
huffman编码实现数据压缩