基本介绍:
- 1)、赫夫曼编码也译为哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,属于一种程序算法。
- 2)、赫夫曼编码是赫夫曼树在电讯通信中经典的应用之一。
- 3)、赫夫曼编码广泛地用于数据文件压缩,其压缩率通常在20%~90%之间。
- 4)、赫夫曼码是可变字长编码(VLC) 的一种。Huffman于1952年提出一种编码方法,称之为最佳编码。
原理剖析:
- eg:content = “i like like like java do you like a java”,共40个字符(包括空格)
- 对应的二进制编码:01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001
- 对应的十进制编码:105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97
- 如果按照二进制来传递信息,信息长度为359(包括空格)
- eg:content = “i like like like java do you like a java”,共40个字符(包括空格)
- 各个字符对应的出现的次数:‘d’:1,‘y’:1,‘u’:1,‘j’:2,‘v’:2,‘o’:2,‘l’:4,‘k’:4,‘e’:4,‘i’:5,‘a’:5,’ ':9。
- 按各个字符出现的次数进行编码,原则是出现的次数越多的,则编码越小,比如space出现了9次,编码为0,其它依次类推:0=’’,1=‘a’,10=‘i’,11=‘e’,100=‘k’,101=‘l’,110=‘o’,111=‘v’,1000=‘j’,1001=‘u’,1010=‘y’,1011=‘d’。
- 按照上面给各个字符规定的编码,则我们在传输“i like like like java do you like a java”数据时,编码就是
(i)10( )0(l)101(i)10(k)100…(以此类推下去) - 字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码,即不能匹配到重复的编码,实际上在这个变长编码方式中是不符合前缀编码要求的,如10作为前缀,即可能是’k’(100=‘k’),也可能是’i’(101=‘l’)。
- eg:content = “i like like like java do you like a java”,共40个字符(包括空格)。
- 各个字符对应的出现的次数:‘d’:1,‘y’:1,‘u’:1,‘j’:2,‘v’:2,‘o’:2,‘l’:4,‘k’:4,‘e’:4,‘i’:5,‘a’:5,’ ':9。
- 按照上面字符出现的次数构建成一棵赫夫曼树,次数作为权值。赫夫曼树的构建原理,请移驾至此处
- 根据赫夫曼树给各个字符规定编码,始终遵循一个原则:向左的路径为0,向右的路径为1
‘o’:1000 ‘u’:10010 ‘d’:100110 ‘y’:100111 ‘i’:101
‘a’:110 ‘k’:1110 ‘e’:1111 ‘j’:0000 ‘v’:0001
‘l’:001 ’ ':01 - 按照上面的赫夫曼编码,无损压缩对应的编码为:
101 01 00110111101111 01 00110111101111 01 00110111101111 01 00001100001110 01
1001101000 01 100111100010010 01 00110111101111 01 00001100001110
长度:133
说明:
1). 原来的长度359,压缩率为(359-133)/359=62.9%
2). 此编码满足前缀编码,即字符的编码都不能是其他字符编码的前缀,不会造成匹配的多义性。
注意:
赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是WPL是一样的,都是最小的。
思路解析:
- 传输的字符串转换成Byte[];
- 获取每个字符出现的次数;
- 构建赫夫曼树;
- 生成赫夫曼树对应的赫夫曼编码;
4.1. 将赫夫曼编码存放在Map<Byte,String>中,形式32->01 97->100 100->11000等等
4.2. 在生成赫夫曼编码表时,需要去拼接路径,定义一个StringBuilder,存储某个叶子节点的路径
4.3. 使用赫夫曼编码对数据进行编码; - 解码赫夫曼编码
5.1. 将压缩完成的byte数组转换成对应的二进制字符串
5.2. 赫夫曼编码对应的二进制字符串转换成原始数据
代码示例
public class HuffmanCodeDemo {
private static Map<Byte, String> huffmanCodeMap = Maps.newHashMap();
private static Map<String, Byte> huffmanDecodeMap = Maps.newHashMap();
private static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) {
String content = "i like like like java do you like a java";
byte[] bytes = huffmanCodeZip(content);
System.out.println(Arrays.toString(bytes));
bytes = huffmanCodeUnzip(bytes);
System.out.println(new String(bytes));
}
private static byte[] huffmanCodeUnzip(byte[] huffmanZipCodeData) {
if (huffmanZipCodeData == null || huffmanZipCodeData.length == 0) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, length = huffmanZipCodeData.length; i < length; i++) {
boolean flag = (i == (length - 1));
stringBuilder.append(byteToString(!flag, huffmanZipCodeData[i]));
}
return getHuffmanDecodeList(stringBuilder);
}
private static byte[] getHuffmanDecodeList(StringBuilder value) {
if (value == null) {
return null;
}
List<Byte> byteList = Lists.newArrayList();
for (int index = 0; index < value.length(); ) {
int count = 1;
boolean flag = true;
Byte tempByteValue = null;
while (flag) {
String key = value.substring(index, index + count);
tempByteValue = huffmanDecodeMap.get(key);
if (tempByteValue == null) {
count++;
} else {
flag = false;
}
}
byteList.add(tempByteValue);
index += count;
}
byte[] bytes = new byte[byteList.size()];
for (int i = 0, length = byteList.size(); i < length; i++) {
bytes[i] = byteList.get(i);
}
return bytes;
}
private static String byteToString(boolean flag, byte byteValue) {
int temp = byteValue;
if (flag) {
temp |= 256;
}
String tempString = Integer.toBinaryString(temp);
if (flag) {
return tempString.substring(tempString.length() - 8);
} else {
return tempString;
}
}
private static byte[] huffmanCodeZip(String content) {
byte[] contentBytes = content.getBytes();
List<CodeNode> codeNodes = getCodeNodes(contentBytes);
CodeNode codeNode = creatHuffmanTree(codeNodes);
getCodes(codeNode);
return zip(contentBytes);
}
private static byte[] zip(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
for (byte item : bytes) {
stringBuilder.append(huffmanCodeMap.get(item));
}
int len = (stringBuilder.length() + 7) / 8;
byte[] huffmanCodeBytes = new byte[len];
int index = 0;
String tempByte;
for (int i = 0; i < stringBuilder.length(); i += 8) {
if ((i + 8) > stringBuilder.length()) {
tempByte = stringBuilder.substring(i);
} else {
tempByte = stringBuilder.substring(i, i + 8);
}
huffmanCodeBytes[index++] = (byte) Integer.parseInt(tempByte, 2);
}
return huffmanCodeBytes;
}
private static void getCodes(CodeNode codeNode) {
if (codeNode == null) {
return;
}
getCodes(codeNode.getLeft(), "0", stringBuilder);
getCodes(codeNode.getRight(), "1", stringBuilder);
}
private static void getCodes(CodeNode codeNode, String code, StringBuilder stringBuilder) {
StringBuilder tempCode = new StringBuilder(stringBuilder);
tempCode.append(code);
if (codeNode == null) {
return;
}
if (codeNode.getValue() == null) {
getCodes(codeNode.getLeft(), "0", tempCode);
getCodes(codeNode.getRight(), "1", tempCode);
} else {
huffmanCodeMap.put(codeNode.getValue(), tempCode.toString());
huffmanDecodeMap.put(tempCode.toString(), codeNode.getValue());
}
}
private static List<CodeNode> getCodeNodes(byte[] contentBytes) {
if (ObjectUtils.isEmpty(contentBytes)) {
return Lists.newArrayList();
}
Map<Byte, Integer> contentMap = Maps.newHashMap();
Integer value;
for (Byte item : contentBytes) {
value = contentMap.getOrDefault(item, 0);
contentMap.put(item, value + 1);
}
List<CodeNode> codeNodes = Lists.newArrayList();
for (Byte item : contentMap.keySet()) {
codeNodes.add(new CodeNode(item, contentMap.get(item)));
}
return codeNodes;
}
private static CodeNode creatHuffmanTree(List<CodeNode> codeNodes) {
if (CollectionUtils.isEmpty(codeNodes)) {
return null;
}
while (codeNodes.size() > 1) {
Collections.sort(codeNodes);
CodeNode leftNode = codeNodes.get(0);
CodeNode rightNode = codeNodes.get(1);
CodeNode parent = new CodeNode(null, leftNode.getWeight() + rightNode.getWeight());
parent.setLeft(leftNode);
parent.setRight(rightNode);
codeNodes.remove(leftNode);
codeNodes.remove(rightNode);
codeNodes.add(parent);
}
return codeNodes.get(0);
}
}
@Data
class CodeNode implements Comparable<CodeNode> {
private Byte value;
private Integer weight;
private CodeNode left;
private CodeNode right;
public CodeNode(Byte value, Integer weight) {
this.value = value;
this.weight = weight;
}
@Override
public int compareTo(CodeNode o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "CodeNode{" +
"value=" + value +
", weight=" + weight +
'}';
}
}