霍夫曼编码
百度定义:霍夫曼编码(Huffman Coding),又称哈夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)
在了解赫夫曼编码前先了解一下,还有要先学会构建 传送门:霍夫曼树
定长编码:太长
变长编码:多义性
哈夫曼编码:牛!!(刚学完霍夫曼树还不知道干啥,直到霍夫曼编码)
一,压缩流程
二,压缩全代码
三,解压流程
四,解压全代码
五,遇到的问题!!!!
赫夫曼编码压缩流程:(整个流程需要传入byte数组,传出byte数组)
-
给定字符串
-
得到每一个字母对应的ASCII码值
… -
统计每个数字出现的次数(即该字母出现的次数)
-
这里将字母和出现的次数,分别分配给Node节点的data属性(数据)和weight属性(权重),并将其排序,得到权值有序的Node节点序列
-
然后对该集合操作成哈夫曼树
-
将赫夫曼树中的叶子节点(即存在的节点)转化为对应的路径形式(路径向左为0,向右为1)
-
将文本用二进制字符串表示
-
将二进制字符串其压缩成字节数组
-
将子方法整合
压缩全代码:
public class HuffmanCoding {
public static void main(String[] args) {
String conText = "i like like like java do you like a java";
zip(conText);
}
//将子方法整合起来
public static void zip(String conText) {
Node huffmanRoot = getHuffmanRoot(conText);//获得哈夫曼根节点
String huffmanCoding = contextHuffmanCoding(huffmanRoot, conText);//根据哈夫曼根节点获得哈夫曼编码
Byte[] bytes = zipHuffman(huffmanCoding);//将哈夫曼编码压缩成哈夫曼byte数组
System.out.println("哈夫曼字节数组:" + Arrays.toString(bytes));
}
//得到哈夫曼树的根节点
public static Node getHuffmanRoot(String conText) {
byte[] bytes = conText.getBytes();
Map<Byte, Integer> map = new HashMap<>();
for (byte aByte : bytes) {
Integer count = map.get(aByte);
if (count == null) {
map.put(aByte, 1);
} else {
map.put(aByte, count + 1);
}
}
ArrayList<Node> nodes = new ArrayList<>();
for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
//得到权值有序的node集合
while (nodes.size() > 1) {
Collections.sort(nodes);//每一次while循环完都是无序的,所以要排序
Node leftNode = nodes.get(0);
Node ringhtNode = nodes.get(1);
//过度节点,详情是关于哈夫曼树的构建
Node parent = new Node(null, leftNode.weight + ringhtNode.weight);
nodes.add(parent);//将父节点加入到集合中
parent.left = leftNode;//设置父节点的左孩子
parent.right = ringhtNode;//设置父节点的右孩子
nodes.remove(leftNode);//移除左孩子
nodes.remove(ringhtNode);//移除右孩子
}
return nodes.get(0);//最后集合里剩下一个元素,该元素就是该huffman树的根节点
}
//6. 得到赫夫曼编码
static HashMap<Byte, String> map = new HashMap<>();//该map存放数字与对应的路径
public static void getRoad(Node node, String code, StringBuilder stringBuilder) {
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
stringBuilder1.append(code);
if (node != null) {
if (node.data == null) {
//向左递归
getRoad(node.left, "0", stringBuilder1);
//向右递归
getRoad(node.right, "1", stringBuilder1);
} else {
map.put(node.data, stringBuilder1.toString());
}
}
}
//将文字用赫夫曼编码表示
public static String contextHuffmanCoding(Node huffmanRoot, String conText) {
//得到哈夫曼树的根节点
//得到每个叶子节点的路径,向左为0,向右为1,(自己可以去重载)
getRoad(huffmanRoot, "", new StringBuilder());
byte[] bytes = conText.getBytes();//得到文字的字节数组
StringBuilder stringBuilder = new StringBuilder();
for (byte aByte : bytes) {
stringBuilder.append(map.get(aByte));
}
//System.out.println(stringBuilder);
return stringBuilder.toString();
}
//将赫夫曼编码压缩成字节数组
public static Byte[] zipHuffman(String huffmanCoding) {
int length = huffmanCoding.length();//133
int byteLength = (length + 7) / 8;//确定我的byte数组的大小
Byte[] bytes = new Byte[byteLength];
int index = 0;//bytes数组的下标
for (int i = 0; i < length; i = i + 8) {
String substring;
if (i + 8 > length) {
substring = huffmanCoding.substring(i);
} else {
substring = huffmanCoding.substring(i, i + 8);
}
//将二进制转化为byte
bytes[index] = (byte) Integer.parseInt(substring, 2);
index++;
}
return bytes;
}
}
赫夫曼编码解压流程:
- 将byte数组转化为二进制字符串
- 将二进制字符串根据map映射转出文本
解压全代码:
public class HuffmanCodingUnpack {
public static void main(String[] args) {
String conText = "i like like like java do you like a java like do you java php c ddddddd";
Byte[] zip = zip(conText);
String toBinary = byteToBinary(zip);
getContext(toBinary);
}
//根据字节数组转化成二级制字符串
public static String byteToBinary(Byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
/**
* 这里有三种情况,
* 一,负数:借助Integer的方法转二进制的方法,截取后8位
* 二,正数:正数转成二进制直接为其二进制形式
* 情况一:在中间的数,用该表达式:temp |= 256;将其变成9位,截取后八位
* 情况二:在最后的数,直接转成二进制即可(不过这里有个怎末确定是最后的一组数,我这里用了统计)
* 如果最后存进来的二进制是01111001类似,映射到byte数组中是121,而121再解压出来是1111001,
* 会导致最后的几位错误,所以这里大家需要再解决这个问题
*/
for (Byte aByte : bytes) {
String substring = "";
if (aByte < 0) {
String s = Integer.toBinaryString(aByte);
substring = s.substring(s.length() - 8);
stringBuilder.append(substring);
index++;
} else {
index++;
if (index < bytes.length) {
int temp = aByte;
temp |= 256;
String s = Integer.toBinaryString(temp);
substring = s.substring(s.length() - 8);
stringBuilder.append(substring);
} else {
String s = map.get(null);
stringBuilder.append(s);
}
}
}
System.out.println(stringBuilder);
return stringBuilder.toString();
}
//将二进制字符串根据map映射转出文本
public static void getContext(String binaryStr) {
//得到HuffmanCodingZip类内的映射关系,并反转放入新的map集合
HashMap<Byte, String> map = HuffmanCodingZip.map;
HashMap<String, Byte> newMap = new HashMap<>();
for (Map.Entry<Byte, String> entry : map.entrySet()) {
newMap.put(entry.getValue(), entry.getKey());
}
StringBuilder stringBuilder = new StringBuilder();//声明新的可拼接字符串
//遍历新的map
for (Map.Entry<String, Byte> entry : newMap.entrySet()) {
System.out.println(entry.getKey() + "==>" + entry.getValue());
}
int index = 0;
for (int i = index; i <= binaryStr.length(); i++) {
String subStr = binaryStr.substring(index, i);
if (newMap.containsKey(subStr)) {
byte b = newMap.get(subStr);
stringBuilder.append((char) b);
index = i ;
}
}
System.out.println("字符串"+stringBuilder);
}
}
对于遇到的一些问题:
对于解压过程中,对于二进制转换成byte数这里有两种情况,
- 负数:借助Integer的方法转二进制的方法,截取后8位
- 正数:正数转成二进制直接为其二进制形式
情况一:在中间的数,用该表达式:temp |= 256;将其变成9位,截取后八位
情况二:(刚开始的想法,后来该想法错误)在最后的数,直接转成二进制即可
(此想法错误的原因)如果最后存进来的二进制是01111001类似,映射到byte数组中是121,而121再解压出来是1111001,会导致最后的几位错误,所以这里大家需要再解决这个问题
解决:小编自己想了一下,既然最后的情况如此难以捉摸,不如就借着(map)这条船,将最后的难以捉摸的二进制码直接存入map集合,如:map.put(null,substring);
然后最后直接从map中获取,尽管消耗了一定空间,但是在另一种程度上减少了,对于二进制码的判断(再者说,最后的一节二进制码对于整个文本来说,如同大海一粟,)
希望大家有其它方法讨论并提供解决方案;(推荐大家去学习韩顺平的数据结构)