代码是根据尚硅谷韩顺平老师写的,发现有错误,先放着留着学习吧。
package com.attongji.huffmancode;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class HuffmanCode {
public static void main(String[] args) {
zipFile("D:\\eclipseworkspace\\DataStructure\\img\\白竞.txt",
"D:\\eclipseworkspace\\DataStructure\\img\\test.zip");
System.out.println("压缩成功!");
unZipFile("D:\\eclipseworkspace\\DataStructure\\img\\test.zip",
"D:\\eclipseworkspace\\DataStructure\\testunzip.txt");
System.out.println("解压成功!");
}
public 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[] huffmanbytes = (byte[]) ois.readObject();
Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();
byte[] bytes = decode(huffmanCodes, huffmanbytes);
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 e2) {
System.out.println(e2.getMessage());
}
}
}
/**
*
* @param srcFile 文件输入路径
* @param dstFile 压缩后文件输出路径
*/
public static void zipFile(String srcFile, String dstFile) {
OutputStream os = null;
ObjectOutputStream oos = null;
FileInputStream is = null;
try {
is = new FileInputStream(srcFile);
byte[] b = new byte[is.available()];
// 将srcFile按字节读进b中
is.read(b);
byte[] huffmanCode = huffmanzip(b);
os = new FileOutputStream(dstFile);
// 因为是字节数组,为了方便不采用FileOutPutStream直接写入
// byte[]中,而是采用ObjectOutPutStream
oos = new ObjectOutputStream(os);
oos.writeObject(huffmanCode);
// 为了方便之后解压,应该把huffman编码表也写入文件
oos.writeObject(huffmanCodes);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
try {
is.close();
oos.close();
os.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
/**
* 前序遍历构造好的huffman树
*
* @param root
*/
public static void preOrder(Node root) {
if (root != null) {
root.preOrder();
} else {
return;
}
}
public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanbytes) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < huffmanbytes.length; i++) {
byte b = huffmanbytes[i];
// 判断是否到了最后一个字符
boolean flag = (i == huffmanbytes.length - 1);
builder.append(byteToString(!flag, b));
}
// 因为是将转化(压缩后得到的)huffman编码解码回原来的字符数组(原始信息)
// 则应该将huffman表转置为键(key)为huffman编码,值(value)为字符
Map<String, Byte> map = new HashMap<String, Byte>();
for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
List<Byte> list = new ArrayList<>();
// 逐个地取出拼接出的二进制string类型的huffman编码,(最终的huffman编码其实是不是二进制的)
// 然后用去转置后的huffman编码表中找,如果有匹配的字符,则继续找,没有的话指针继续前进
// 这里之所以用list存放解码回来的字符,是因为不知道字符到底有多少,是不确定长度的。
for (int i = 0; i < builder.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
while (flag) {
String key = builder.substring(i, i + count);
b = map.get(key);
if (b == null) {
count++;
} else {
flag = false;
}
}
list.add(b);
i += count;
}
byte b[] = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
/**
* 该方法是将b(就是转化为huffmancode形式的字符)转化回原来的二进制形式
*
* @param flag
* @param b
* @return
*/
public static String byteToString(boolean flag, byte b) {
int temp = b;
if (flag) {
// 256的二进制字节码为1000 0000,如果flag为true,
// 说明是中间需要拼接的字符
// 中间需要凭借的字符则应注意对高位补全,例如如果中间出现了正数1
// 其二进制就为1,不足8位,与256位或之后为1000 0001;
// 又比如中间出现了6,其二进制为110,也不足8位,则需要补全
// 与256位或之后位1000 0110
temp |= 256;
}
String str = Integer.toBinaryString(temp);
if (flag) {
return str.substring(str.length() - 8);
} else {
return str;
}
}
// 使用一个方法将上述方法进行封装
public static byte[] huffmanzip(byte[] bytes) {
// Step.1 将字符数组按照出现频次作为权重封装为Node,并将Node放入List集合
List<Node> nodes = getNodes(bytes);
// Step.2 根据list创建huffman树
Node root = createHuffmanTree(nodes);
// Step.3 根据huffman树的叶子节点创建出各个叶子节点的权重路径huffman编码表
Map<Byte, String> map = getCodes(root);
// step.4 根据创建出来的huffman编码表去和待处理的bytes数组对比从而得到最终的压缩后的结果
byte[] resultByte = zip(bytes, map);
return resultByte;
}
/**
* 给定一个byte数组和huffman编码数组,将这个byte数组进行 压缩。相当于对下面的几个方法进行封装。
*
* @param bytes
* @param huffmanCodes
* @return
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 先利用一个Stringbuilder对byte数组中的元素对应的huffman编码进行拼接
StringBuilder builder = new StringBuilder();
for (byte b : bytes) {
builder.append(huffmanCodes.get(b));
}
// 接下来的操作是上面拼接得到的huffman编码再简化一下
// 每8个编码为一组重新装到一个byte[]数组中
int len;
if (builder.length() % 8 == 0) {
len = builder.length() / 8;
} else {
len = builder.length() / 8+ 1;
}
byte[] huffmanCodeBytes = new byte[len];
int index = 0;
for (int i = 0; i < builder.length(); i += 8) {
String strByte;
if (i + 8 > builder.length()) {
strByte = builder.substring(i);
} else {
strByte = builder.substring(i, i + 8);
}
// 这一步是将占用内存较大的不可用编码转化为压缩率更高的最终huffman编码的关键
// 采用了包装类Integer.parseInt()方法
huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte,2);
}
return huffmanCodeBytes;
}
/**
* 为了调用方便,重载该方法
*/
public static Map<Byte, String> getCodes(Node root) {
if (root == null) {
return null;
}
getCodes(root.leftNode, "0", builder);
getCodes(root.rightNode, "1", builder);
return huffmanCodes;
}
// 提前创建一个map用来存储哥哥字符对应的huffman码
private static Map<Byte, String> huffmanCodes = new HashMap<>();
// 初始利用huffman树叶子节点的带权路径拼接出的huffman编码是0、1组成的不可读或者长度
// 及内存占用较大的不善编码,这种编码的压缩率较差,所以需要每8位转化为占用内存更加小的
// 可用编码。
private static StringBuilder builder = new StringBuilder();
/**
* 该方法的目的是将传入的构建好的huffman树的根节点得到 其叶子节点对应的huffman编码
*
* @param root:传入的huffman树的根节点
* @param code:用来标识左子路还是右子路
* @param builder:用于拼接路径(即对应的huffman编码)
*/
public static void getCodes(Node root, String code, StringBuilder builder) {
// 利用非叶子节点的data为null这个特点作为是否接着进行递归调用
// 的凭据
StringBuilder builder2 = new StringBuilder(builder);
builder2.append(code);
if (root.data == null) {
getCodes(root.leftNode, "0", builder2);
getCodes(root.rightNode, "1", builder2);
} else {
huffmanCodes.put(root.data, builder2.toString());
}
}
/**
* 将存放字符的数组包装成node之后装入List中,这样方便之后HuffmanTree的创建
*
* @param bytes
* @return
*/
public static List<Node> getNodes(byte[] bytes) {
// 该map是用来统计每个字符在数组中出现的次数
Map<Byte, Integer> map = new HashMap<Byte, Integer>();
for (byte b : bytes) {
Integer count=map.get(b);
if (count == null) {
map.put(b, 1);
} else {
map.put(b, count+1);
}
}
// 创建一个list来存放根据字符(key)、出现次数(值,也就是权值)
// 包装的Node类
List<Node> nodes = new ArrayList<>();
for (Entry<Byte, Integer> entry : map.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
* 根据传入的node组成的集合创建huffman树
*
* @param nodes
* @return
*/
public static Node createHuffmanTree(List<Node> nodes) {
// 逻辑上list每挤出两个node,又添加进一个node
// 所以当list的尺寸大于等于2就可以接着进行huffman二叉树的创建
while (nodes.size() > 1) {
Collections.sort(nodes);
Node n1 = nodes.get(0);
Node n2 = nodes.get(1);
Node newNode = new Node(null, n1.weight + n2.weight);
newNode.leftNode = n1;
newNode.rightNode = n2;
nodes.remove(n1);
nodes.remove(n2);
nodes.add(newNode);
}
return nodes.get(0);
}
}
class Node implements Comparable<Node> {
Byte data;// 只有叶子节点才有,就是字符对应的数字
int weight;// 权重值,最初指的是每个字符出现的次数
Node leftNode;
Node rightNode;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
return this.weight - o.weight;
}
/**
* 每个节点都有自己前序遍历的逻辑实现
*/
public void preOrder() {
System.out.println(this);
if (this.leftNode != null) {
this.leftNode.preOrder();
}
if (this.rightNode != null) {
this.rightNode.preOrder();
}
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
}