1 问题描述
1.1 问题位置
将单个byte解码为对应码字的处理方式上的问题
1.2 具体问题列举
- 解码时,最后一个byte元素的处理问题:如果像韩老师那样是最后一位byte,即不补高位,也不截取直接转换为二进制会出现译码错误的问题.比如 最后一位byte如果是2 那作为接收方我们无法得知 原来的编码到底是 10 还是 010 还是0010...如果这样处理会导致比较严重的译码错误.
- 梳理除最后一个元素的其他元素处理的思路:对于其余元素来说,由于使用的Integer.toBinaryString()方法会直接返回正数本身,因此我们提前为其补零.那么为什么要用256或运算来补呢?这是因为256的二进制是1 0000 0000 因此,作为一个正数,与其或运算再截取后八位不会改变其值.对于负数,与其或运算再截取也不会改变值,故我们直接统一:直接把不是最后一个元素的byte都来或上256 转换为二进制后再截取后八位 就可以了.
2 解决思路
2.1对于最后一个byte元素
思路
为了能够感知到最后一位的byte到底是由什么样的码字编码而来的,我们增加静态属性count来记录编码时最后一位的byte被转换之前的码字的前导零的个数 比如 生成到了最后一个发现byte 还剩余码字0011100,我们从i处循环记录0的个数,首次遇到非零就退出不在计数即可
[注意]此时还要修改编码生成byte[] 的方法,对于最后刚好够8位的情况我们也要统计一下前导零的个数,比如若最后是剩00011100也属于难以译码的范围,也需要count来记录,否则我们一译码就成了11100,导致出现错误
实现
bytesToStringBitCode方法
/**
* TODO 把接收到的bytes[] 哈夫曼编码的byte数组 还原成 一位一位的字符串码
*
* @param b 哈夫曼编码的byte数组中的某个元素
* @return b对应的哈夫曼编码
*/
public static String bytesToStringBitCode(byte b, boolean isLast) {
int temp = b;
//只要不是最后一个我就让你和256或一下,对于负数,或完是原封不动的,对于正数,或完可以补领,
// 至于多出的一位我们可以通过截取来避免改变正数.
/*
总结:由于后序会截断 只取后八个
故 或上256
对于负数没有影响
对于正数既可以补上0,又可以防止其数值产生变化
因此只要不是最后一个我们就或256
*/
if (!isLast) {
temp = temp | 256;
}
/*
而对于最后一位,如果是正数,对于我们接收方,是完全未知接收到的(举例)2原编码是 010 还是10 还是 0010 还是00010
故,在编码时我们记录一下就好了,zip方法中,只要是为最后一个bytes元素压缩,就记录0的个数,首次遇到非零我就退出,这样就能
记录0的个数,接收方就可以通过属性count来避免译码时出现严重错误
*/
if (isLast) {
//最后一位我们不做或256补齐高位的操作,即使原来编码有零也不怕,因为编码时count已经帮我们记录过了前导0的个数
//如果运气好是负数,那就更好直接就是八位,
//如果是正数且有零,那就补零,补上count个前导零就OK了
StringBuilder lastStrCode = new StringBuilder();
for (int i = 0; i < count; i++) {
lastStrCode.append("0");
}
lastStrCode.append(Integer.toBinaryString(temp));
return lastStrCode.toString();
}
String str = Integer.toBinaryString(temp);
str = str.substring(str.length() - 8);
return str;
}
zip方法
只需注意该方法中最后一个byte的处理细节即可,其它代码和韩老师的讲解完全一致
/**
* TODO ************字符串消息的压缩方法*************
* 获取到的全部哈夫曼编码是字符串:类似于 "01101010110111010101..."
* 这是二进制码,再将其八个一位转换成byte,存入byte[]中,全部存完以后就是我们要返回的byte数组
* 如10101000转换成byte
* 10101000(补码) 10101000-1=10100111(反码) 10100111取反11011000(原码)
* 最前面的位是符号位,1表示负数
* 2^3+2^4+2^6=88
* 故最终转换成byte为-88
*
* @param bytes 字符串对应的原始字符数组
* @param huffmanCodeMap 哈夫曼编码对照表(这是根据字符串消息生成的)
* @return 压缩编码后的byte数组, 存放哈夫曼编码
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodeMap) {
StringBuilder code = new StringBuilder();
//获得bytes对应的全部的字符串哈夫曼编码
for (byte aByte : bytes) {
code.append(huffmanCodeMap.get(aByte));
}
// 用于测试 System.out.println(code.toString());
//把字符串编码转换为byte[]
//计算byte[]长度
int len = (code.length() % 8 == 0) ? (code.length() / 8) : (code.length() / 8) + 1;
byte[] huffmanCodeBytes = new byte[len];
//为循环做准备工作
String curCode;
int index = 0;
for (int i = 0; i < code.length(); i = i + 8) {
//最后一位
if ((i + 8) >= code.length()) {
//下面改写了code是用于测试含前导零编码时是否能正确译码,
// 并且,此处的进入条件要包括刚好够八位的情况,因此,此处要改为>=code.length
// code= new StringBuilder("10101000101111111100100010111111110010001011111111001001010011" +
// "01110001110000011011101000111100101000101111111100110001001010011000011100");
for (int j = i; j < code.length(); j++) {
if (code.substring(j,j+1).equals("0")) {
count++;
} else {
break;
}
}
curCode = code.substring(i);
} else {//其他位
curCode = code.substring(i, i + 8);
}
huffmanCodeBytes[index++] = (byte) Integer.parseInt(curCode, 2);
}
return huffmanCodeBytes;
}
2.2对于非最后一个的byte元素
若不是最后一个byte我们都选择或上256,再转换成二进制,再截取后八位
实现
具体实现可以查看2.1的实现中的bytesToStringBitCode方法
3 总结
- 要注意增加静态属性 count 用于记录编码生成byte[ ]的最后一位时前导零的个数
- 测试时要避免多次压缩 比如 写完压缩方法韩老师会带我们测试 写完整体封装代码也会带我们测试,多次测试会导致count翻倍(因为是静态属性),后序译码会有问题,注意测试时不要多次编码
- 要注意编码时无论最后一个byte 用了8个还是 8个一下的二进制码,我们都要记录其前导零的个数!
- 如果想测试特殊情况(编码时最后一位byte用的码字是正数且含前导零),可以查看我提供的zip方法中的处理最后一位byte的注释,可以打开注释用于测试,注释内我们把编码修改了一下,让他符合特殊情况.打开后经本人测试,可以完成正确的译码.
文章讨论了解码单个byte时可能出现的译码错误,特别是最后一个byte的处理问题。提出使用静态属性count记录非最后一个byte元素的前导零,以及对最后一个byte特殊处理的方法。最后强调测试时需注意编码时的特殊情况以确保正确译码。
2783

被折叠的 条评论
为什么被折叠?



