- 赫夫曼编码也翻译为哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,属于一种程序算法
- 赫夫曼编码是赫夫曼树在电讯通信中的经典应用之一
- 赫夫曼编码广泛的用于数据文件压缩。其压缩率通常在 20% - 90% 之间
- 赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方式,称之为最佳编码
package huffmancode;
import java.io.*;
import java.util.*;
public class HuffmanCode {
public static void main(String[] args) {
String content = "i like like like java do you like a java";
byte[] contentBytes = content.getBytes();
//压缩
byte[] bytes = huffmanZip(contentBytes);
//解压
byte[] decode = decode(huffmanCodes, bytes);
System.out.println(new String(decode));
//压缩文件
String srcFile = "d://test.txt";
String dstFile = "d://test.zip";
zipFile(srcFile,dstFile);
//解压文件
unZipFile("d://test.zip","d://upzip.png");
}
/**
* 功能:文件解压
* @param zipFile 需要解压的文件路径
* @param dstFile 解压后文件存放路径
*/
private static void unZipFile(String zipFile,String dstFile){
InputStream is = null;
ObjectInputStream ois = null;
OutputStream os = null;
try{
is = new FileInputStream(zipFile);
ois = new ObjectInputStream(is);
//读取byte数组
byte[] huffmanBytes = (byte[])ois.readObject();
//读取赫夫曼编码表
Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();
//解码
byte[] bytes = decode(huffmanCodes, huffmanBytes);
//将bytes数组写入目标文件
os = new FileOutputStream(dstFile);
os.write(bytes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try{
os.close();
ois.close();
is.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
/**
* 功能:文件压缩
* @param srcFile 需要压缩的文件全路径
* @param dstFile 压缩后文件的存放路径
*/
private static void zipFile(String srcFile,String dstFile){
//创建输入流
FileInputStream is = null;
//创建输出流
FileOutputStream os = null;
ObjectOutputStream oos = null;
try{
is = new FileInputStream(srcFile);
//创建一个和源文件大小一样的byte[]
byte[] b = new byte[is.available()];
//读取文件
is.read(b);
//对文件进行压缩
byte[] huffmanBytes = huffmanZip(b);
//创建文件的输出流,存放压缩文件
os = new FileOutputStream(dstFile);
//创建一个和文件输出流关联的ObjectOutputStream
oos = new ObjectOutputStream(os);
//把赫夫曼编码后的字节数据写入压缩文件
oos.writeObject(huffmanBytes);
//把赫夫曼编码写入压缩文件
oos.writeObject(huffmanCodes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try{
is.close();
oos.close();
os.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
/**
* 功能:完成对压缩数据的解码
* @param huffmanCodes 赫夫曼编码表
* @param huffmanBytes 赫夫曼编码的到的字节数组
* @return 原来的字符串对应的数组
*/
private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes){
//得到HuffmanBytes对应的二进制的字符串
StringBuilder stringBuilder = new StringBuilder();
//将byte数组转成二进制的字符串
for(int i =0;i<huffmanBytes.length;i++){
//判断是否为最后一个字节
if (i == huffmanBytes.length-1){
stringBuilder.append(byteToBitString(false,huffmanBytes[i]));
}else {
stringBuilder.append(byteToBitString(true,huffmanBytes[i]));
}
}
//把字符串按照指定的和福安编码进行解码
//把赫夫曼编码表进行调换
Map<String,Byte> map = new HashMap<String,Byte>();
for (Map.Entry<Byte,String> entry:huffmanCodes.entrySet()){
map.put(entry.getValue(),entry.getKey());
}
//创建一个集合存放byte
List<Byte> list = new ArrayList<Byte>();
for (int i =0;i<stringBuilder.length();){
int count = 1;
boolean flag = true;
Byte b = null;
while (flag){
String key = stringBuilder.substring(i,i+count);
b = map.get(key);
if (b == null){ //没有匹配到
count++;
}else { //匹配到
flag = false;
}
}
list.add(b);
i += count;
}
//当循环结束,list中存放了所有的字符
byte b[] = new byte[list.size()];
for (int i =0;i<b.length;i++){
b[i] = list.get(i);
}
return b;
}
/**
* 功能:将一个byte转成一个二进制的字符串
* @param flag 标志是否需要补高位
* @param b 传入的byte
* @return 传入byte对应的二进制字符串(补码)
*/
private static String byteToBitString(boolean flag,byte b){
int temp = b;
if (flag){
temp |= 256; //按位与256
}
String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制补码
if(flag){
return str.substring(str.length() - 8);
}else {
return str;
}
}
//封装编码的方法
/**
*
* @param bytes 原始的字符串对应的字节数组
* @return 经过赫夫曼编码处理后的字节数组(压缩后的数组)
*/
private static byte[] huffmanZip(byte[] bytes){
//根据字节数组,得到Node结点集合
List<Node> nodes = getNodes(bytes);
//传入Node集合创建赫夫曼树
Node huffmanTreeRoot = createHuffmanTree(nodes);
//根据赫夫曼树或去赫夫曼编码
Map<Byte,String> huffmanCodes = getCodes(huffmanTreeRoot);
//压缩
byte[] huffmanCodeBytes = zip(bytes,huffmanCodes);
return huffmanCodeBytes;
}
//编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩的后的byte[]
private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
//利用huffmanCode将bytes转成赫夫曼编码对应的字符串
StringBuilder stringBuilder = new StringBuilder();
//遍历bytes数组
for (byte b:bytes){
stringBuilder.append(huffmanCodes.get(b));
}
//len 存储stringBuilder需要的字节数组长度
int len;
if (stringBuilder.length()%8 == 0){
len = stringBuilder.length() / 8;
}else {
len = stringBuilder.length() / 8 + 1;
}
//创建存储压缩后的byte数组
byte[] huffmanCodeBytes = new byte[len];
int index = 0; //记录是第几个byte
for (int i =0;i<stringBuilder.length();i += 8){ //因为每8位对应一个byte,所以步长为8
String strByte;
if (i+8 > stringBuilder.length()){
strByte = stringBuilder.substring(i);
}else {
strByte = stringBuilder.substring(i,i+8);
}
//将strByte转成一个byte,放入到huffmanCodeBytes
huffmanCodeBytes[index] = (byte)Integer.parseInt(strByte,2);
index++;
}
return huffmanCodeBytes;
}
//将赫夫曼编码表存放在Map<Byte,String>形式
static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
//在生成赫夫曼编码表,需要去拼接路径
static StringBuilder stringBuilder = new StringBuilder();
//为了调用方便,重载getCodes
private static Map<Byte,String> getCodes(Node root){
if (root == null){
return null;
}
//处理root的左子树
getCodes(root.left,"0",stringBuilder);
//处理root的右子树
getCodes(root.right,"1",stringBuilder);
return huffmanCodes;
}
/***
* 功能:将传入的node结点的所有叶子节点的赫夫曼编码得到,并放入到huffmanCodes集合
* @param node 传入的赫夫曼树根结点
* @param code 路径:左子节点 0 右子节点1
* @param stringBuilder 用于拼接路径
*/
private static void getCodes(Node node,String code,StringBuilder stringBuilder){
StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
//将code加入到stringBuilder2
stringBuilder2.append(code);
if (node != null){ //如果node == null 不处理
//判断当前节点node是否为叶子节点
if (node.data == null){ //非叶子节点
//递归处理
//向左递归
getCodes(node.left,"0",stringBuilder2);
//向右递归
getCodes(node.right,"1",stringBuilder2);
}else { //叶子节点
huffmanCodes.put(node.data,stringBuilder2.toString());
}
}
}
/**
* 功能:根据传入的字节数组,转换成List<Node> (data->字节 weight->出现的次数)
* @param bytes 字节数组
* @return List<Node>
*/
private static List<Node> getNodes(byte[] bytes){
//创建一个list
ArrayList<Node> nodes = new ArrayList<Node>();
//统计每个byte出现的次数
HashMap<Byte, Integer> counts = new HashMap<Byte, Integer>();
for (byte b : bytes){
Integer count = counts.get(b);
if (count == null){
counts.put(b,1);
}else {
counts.put(b,count+1);
}
}
//把每个map键值对转成Node,并加入到nodes集合中
for (Map.Entry<Byte,Integer> entry: counts.entrySet()){
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
return nodes;
}
//通过list创建对应的赫夫曼树
private static Node createHuffmanTree(List<Node> nodes){
while (nodes.size() > 1){
//排序
Collections.sort(nodes);
//取出第一棵最小的二叉树
Node leftNode = nodes.get(0);
//取出第二课最小的二叉树
Node rightNode = nodes.get(1);
//创建一个新的二叉树,它的根结点没有data,只有权值
Node parentNode = new Node(null,leftNode.weight + rightNode.weight);
parentNode.left = leftNode;
parentNode.right = rightNode;
//将已经处理的两棵二叉树从nodes中删除
nodes.remove(leftNode);
nodes.remove(rightNode);
//将新的二叉树加入到nodes中
nodes.add(parentNode);
}
return nodes.get(0);
}
}
//创建Node,带数据和权值
class Node implements Comparable<Node>{
Byte data; //存放字符
int weight; //权值,表示字符出现的次数
Node left;
Node right;
public Node(Byte data,int weight){
this.data = data;
this.weight = weight;
}
public int compareTo(Node o) {
//从小到大排序
return this.weight - o.weight;
}
@Override
public String toString() {
return "data:"+this.data +",weight:"+ this.weight;
}
//前序遍历
public void preOrder(){
//输出当前节点值
System.out.println("data:"+this.data + ", weight:"+ this.weight);
//向左遍历
if (this.left != null){
this.left.preOrder();
}
//向右遍历
if (this.right != null){
this.right.preOrder();
}
}
}