赫夫曼编码的数据解压
上篇是说了用赫夫曼编码进行数据压缩,现在实现对压缩数据的解压。
核心步骤
- 将原来压缩成byte数组的编码转成一个二进制字符串
- 对转成的二进制字符串与赫夫曼码表上的数据进行匹配 从而还原原本数据的byte数组
- 先将原先码表反转 因为先要通过 value 找key
- 对二进制字符串进行遍历 使用指针一直扫描字符串,每扫描到一个字符去码表中匹配,如果匹配到了说明该字符串匹配到了原本的一个byte,加入到一个集合中
核心方法
/**
* 将压缩之后的赫夫曼编码重新还原成原字节数组
*
* @param huffmanZipCode
* @return
*/
public static byte[] unZip(byte[] huffmanZipCode, Map<Byte, String> huffmanCodeTable) {
// 1. 现将原来压缩成byte数组的编码 还原成二进制字符串
StringBuilder builder = new StringBuilder();
for (int i = 0; i < huffmanZipCode.length; i++) {
byte b = huffmanZipCode[i];
//判断是不是最后一个字节
boolean flag = (i == huffmanZipCode.length - 1);
builder.append(byteToBitString(!flag, b));
}
// 2. 比对赫夫曼编码表找到码表上对应原来的 byte
// 2.1 现将码表的KV反转 因为现在我们是要通过字符串来找byte 反转之后方便操作
Map<String, Byte> reversHumanCodeMap = new HashMap<>();
for (Byte key : huffmanCodeTable.keySet()) {
reversHumanCodeMap.put(huffmanCodeTable.get(key), key);
}
// 2.2 因为现在需要将拼接好的二进制编码重新转成原文本byte,需要一根指针
// 一直扫描字符串,不断的从反转的码表中找,如果找到了匹配说明是一个byte,不然再追加一个字符继续码表中找
// 创建集合用于保存从码表中匹配到的byte
List<Byte> originalBytesList = new ArrayList<>();
int index = 0;
// 暂存拼接字符串
String tempStr = "";
while (index < builder.length()) {
tempStr += builder.substring(index, index + 1);
Byte originalByte = reversHumanCodeMap.get(tempStr);
if (originalByte != null) {
// 说明码表上找到了一个匹配的byte 重置tempStr
tempStr = "";
originalBytesList.add(originalByte);
}
index++;
}
// list转成byte数组
byte[] originalContentBytes = new byte[originalBytesList.size()];
for (int i = 0; i < originalBytesList.size(); i++) {
originalContentBytes[i] = originalBytesList.get(i);
}
return originalContentBytes;
}
/**
* 将一个byte 转成一个二进制的字符串
*
* @param b 传入的 byte
* @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
* @return 是该b 对应的二进制的字符串,(注意是按补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
//使用变量保存 b
int temp = b; //将 b 转成 int
//如果是正数我们还存在补高位
if (flag) {
temp |= 256; //按位与 256 1 0000 0000 | 0000 0001 => 1 0000 0001
}
String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
使用赫夫曼编码压缩和解压文件
压缩
- 读取文件到一个byte数组中,调用赫夫曼编码压缩方法将数据压缩
- 将压缩有的byte数组使用OjbectOutputStream输出到一个文件中
- 继续使用该输出流将赫夫曼编码表也输出到该文件中 便于以后使用码表进行数据的恢复
解压
- 使用ObjectInputStream读取压缩的字节数组和赫夫曼编码表
- 使用码表对数据进行解压即可
赫夫曼编码完整代码
public class HuffmanCodeDemo {
public static void main(String[] args) {
// 压缩和解压字符串
// String content = "i like like like java do you like a java";
// System.out.println(content.getBytes().length);
// byte[] zipBytes = zip(content.getBytes());
// byte[] originalContent = unZip(zipBytes, huffmanCodeTable);
// System.out.println(new String(originalContent));
// 压缩和解压文件
zipFile("E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test.txt","E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test-copy.zip");
unZipFile("E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test-copy.zip","E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test666.txt");
}
/**
* 赫夫曼码表
*/
public static Map<Byte, String> huffmanCodeTable = new HashMap<>();
/**
* 解压缩文件
* @param srcFile
* @param destFile
*/
public static void unZipFile(String srcFile, String destFile) {
FileInputStream fis = null;
ObjectInputStream ois = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
ois = new ObjectInputStream(fis);
// 读取压缩后的赫夫曼编码
byte[] zipByteCode = (byte[]) ois.readObject();
// 读取赫夫曼码表
Map<Byte, String> huffmanCodeTable = (Map<Byte, String>) ois.readObject();
byte[] originalContentBytes = unZip(zipByteCode, huffmanCodeTable);
fos = new FileOutputStream(destFile);
// 写出
fos.write(originalContentBytes);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用赫夫曼编码压缩文件
*
* @param srcFile
* @param destFile
*/
public static void zipFile(String srcFile, String destFile) {
FileInputStream fis = null;
ObjectOutputStream oos = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
oos = new ObjectOutputStream(fos);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
byte[] zipBytes = zip(bytes);
// 写入赫夫曼压缩之后的字节数组
oos.writeObject(zipBytes);
// 将赫夫曼码表也要写入!!不然无法读取码表解码
oos.writeObject(huffmanCodeTable);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
oos.close();
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将压缩之后的赫夫曼编码重新还原成原字节数组
*
* @param huffmanZipCode
* @return
*/
public static byte[] unZip(byte[] huffmanZipCode, Map<Byte, String> huffmanCodeTable) {
// 1. 现将原来压缩成byte数组的编码 还原成二进制字符串
StringBuilder builder = new StringBuilder();
for (int i = 0; i < huffmanZipCode.length; i++) {
byte b = huffmanZipCode[i];
//判断是不是最后一个字节
boolean flag = (i == huffmanZipCode.length - 1);
builder.append(byteToBitString(!flag, b));
}
// 2. 比对赫夫曼编码表找到码表上对应原来的 byte
// 2.1 现将码表的KV反转 因为现在我们是要通过字符串来找byte 反转之后方便操作
Map<String, Byte> reversHumanCodeMap = new HashMap<>();
for (Byte key : huffmanCodeTable.keySet()) {
reversHumanCodeMap.put(huffmanCodeTable.get(key), key);
}
// 2.2 因为现在需要将拼接好的二进制编码重新转成原文本byte,需要一根指针
// 一直扫描字符串,不断的从反转的码表中找,如果找到了匹配说明是一个byte,不然再追加一个字符继续码表中找
// 创建集合用于保存从码表中匹配到的byte
List<Byte> originalBytesList = new ArrayList<>();
int index = 0;
// 暂存拼接字符串
String tempStr = "";
while (index < builder.length()) {
tempStr += builder.substring(index, index + 1);
Byte originalByte = reversHumanCodeMap.get(tempStr);
if (originalByte != null) {
// 说明码表上找到了一个匹配的byte 重置tempStr
tempStr = "";
originalBytesList.add(originalByte);
}
index++;
}
// list转成byte数组
byte[] originalContentBytes = new byte[originalBytesList.size()];
for (int i = 0; i < originalBytesList.size(); i++) {
originalContentBytes[i] = originalBytesList.get(i);
}
return originalContentBytes;
}
/**
* 将一个byte 转成一个二进制的字符串
*
* @param b 传入的 byte
* @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
* @return 是该b 对应的二进制的字符串,(注意是按补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
//使用变量保存 b
int temp = b; //将 b 转成 int
//如果是正数我们还存在补高位
if (flag) {
temp |= 256; //按位与 256 1 0000 0000 | 0000 0001 => 1 0000 0001
}
String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
/**
* 将一个byte转成二进制字符串
*
* @param b
* @return
*/
public static String byteToBinaryString(byte b) {
return Integer.toBinaryString((b & 0xFF) + 0x100).substring(1);
}
/**
* 得到赫夫曼编码并压缩的方法整合
*
* @param content
* @return
*/
public static byte[] zip(byte[] content) {
HuffmanNode huffmanTree = createHuffmanNode(content);
getHuffmanCode(huffmanTree, "");
return getHuffmanZipCode(content, huffmanCodeTable);
}
/**
* @param content 原始文本的byte
* @param huffmanCodeMap
* @return
*/
public static byte[] getHuffmanZipCode(byte[] content, Map<Byte, String> huffmanCodeMap) {
// 1. 将原始字符串的字节数从对应的赫夫曼码表中找 然后拼接成一个新的字符串
StringBuilder builder = new StringBuilder();
for (byte b : content) {
builder.append(huffmanCodeMap.get(b));
}
// 2.每8位封装成一个byte数字 先获取数组长度
int len;
if (builder.length() % 8 == 0) {
len = builder.length() / 8;
} else {
len = builder.length() / 8 + 1;
}
byte[] zipContent = new byte[len];
int index = 0;
for (int i = 0; i < builder.length(); i += 8) {
String subContent;
if (i + 8 > builder.length()) {
subContent = builder.substring(i);
} else {
subContent = builder.substring(i, i + 8);
}
zipContent[index++] = (byte) Integer.parseInt(subContent, 2);
}
return zipContent;
}
/**
* 根据赫夫曼树生成赫夫曼编码 规定向左为0 向右为1
*
* @param node 赫夫曼树
* @param code 原始赫夫曼编码
* @return
*/
public static void getHuffmanCode(HuffmanNode node, String code) {
if (node != null) {
// 非数据节点
if (node.data == null) {
// 向左递归
// 向右递归
getHuffmanCode(node.left, code + "0");
getHuffmanCode(node.right, code + "1");
} else {
huffmanCodeTable.put((byte) node.data, code);
}
}
}
/**
* 将传入的字符串 拆分成byte 统计 每个byte 出现的次数然后生成一颗赫夫曼树
*
* @param contentBytes
* @return
*/
public static HuffmanNode createHuffmanNode(byte[] contentBytes) {
if (null == contentBytes || contentBytes.length == 0) {
return null;
}
// 1. 字符出现次数统计
Map<Byte, Integer> wordCount = new HashMap<>();
for (byte b : contentBytes) {
if (wordCount.get(b) == null) {
wordCount.put(b, 1);
} else {
wordCount.put(b, wordCount.get(b) + 1);
}
}
// 2. 将统计出来的字符串转成一个Node集合中用于生成 赫夫曼树
List<HuffmanNode> huffmanNodes = new ArrayList<>();
for (Byte key : wordCount.keySet()) {
huffmanNodes.add(new HuffmanNode(key, wordCount.get(key)));
}
// 3. 将Node集合构建成一颗赫夫曼树
while (huffmanNodes.size() > 1) {
Collections.sort(huffmanNodes);
HuffmanNode leftNode = huffmanNodes.get(0);
HuffmanNode rightNode = huffmanNodes.get(1);
HuffmanNode parent = new HuffmanNode(null, leftNode.weight + rightNode.weight);
parent.left = leftNode;
parent.right = rightNode;
huffmanNodes.remove(leftNode);
huffmanNodes.remove(rightNode);
huffmanNodes.add(parent);
}
return huffmanNodes.get(0);
}
}