哈夫曼树是带权路径最短的树,权值加大的节点离根节点较近.
示例代码如下:
public class HuffmanTreeCode {
public static void main(String[] args) {
HuffmanTreeDemo huffmanTree = new HuffmanTreeDemo();
// 压缩
huffmanTree.zipFile("d://3.bmp","d://111.zip");
// 解压缩
huffmanTree.unZipFile("d://111.zip","d://3.bmp");
}
}
class HuffmanTreeDemo{
Map huffmanCodes = new HashMap();
//在生成赫夫曼编码表示,需要去拼接路径, 定义一个StringBuilder 存储某个叶子结点的路径
StringBuilder stringBuilder = new StringBuilder();
/**
* 文件解压缩
* @param zipFile 压缩文件全路径
* @param dstFile 解压文件的全路径
*/
public void unZipFile(String zipFile,String dstFile) {
FileInputStream is = null;
ObjectInputStream ois = null;
FileOutputStream os = null;
try {
// 创建文件输入流
is = new FileInputStream(zipFile);
// 创建关联的对象输入流
ois = new ObjectInputStream(is);
// 读入压缩文件和对应的哈夫曼编码表
byte[] zipBytes = (byte[])ois.readObject();
this.huffmanCodes = (Map)ois.readObject();
// 解压缩文件
byte[] decode = decode(zipBytes);
// 将解压缩的字节数组写入到目标文件中
os = new FileOutputStream(dstFile);
os.write(decode);
} catch (Exception e) {
System.out.println(e.getMessage());
}finally {
try {
os.close();
ois.close();
is.close();
} catch (Exception e1) {
System.out.println(e1.getMessage());
}
}
}
/**
* 文件压缩
* @param srcFile 源文件绝对路径
* @param dstFile 目标文件绝对路径
*/
public void zipFile(String srcFile,String dstFile) {
FileInputStream is =null;
FileOutputStream os =null;
ObjectOutputStream oos=null;
try {
//创建文件输入流
is = new FileInputStream(srcFile);
//创建对应的字节数组
byte[] srcBytes = new byte[is.available()];
// 读取文件
is.read(srcBytes);
// 获取哈夫曼压缩后的字节数组
byte[] zipBytes = getHuffmanZipBytes(srcBytes);
// 创建文件输出流
os = new FileOutputStream(dstFile);
// 创建对应的对象输出流
oos = new ObjectOutputStream(os);
//将压缩后的字节数组写入目标文件
oos.writeObject(zipBytes);
//将对应的哈夫曼编码写入文件中(一定要写,为了将来解压缩用)
oos.writeObject(this.huffmanCodes);
} catch (IOException e) {
System.out.println(e.getMessage());
}finally {
try {
oos.close();
os.close();
is.close();
} catch (Exception e1) {
System.out.println(e1.getMessage());
}
}
}
/**
* 解码
* @param huffmanZipBytes 哈夫曼压缩后的字节数组
* @return
*/
public byte[] decode( byte[] huffmanZipBytes) {
if (huffmanZipBytes == null || this.huffmanCodes==null) {
return null;
}
Map decodeMap = new HashMap<>(this.huffmanCodes.size());
for (Map.Entry entry : this.huffmanCodes.entrySet()) {
decodeMap.put(entry.getValue(), entry.getKey());
}
// 将压缩后的字节数组恢复成二进制字符串
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < huffmanZipBytes.length; i++) {
boolean flag = (i == huffmanZipBytes.length - 1);
// 不是数组的末尾元素的需要补齐高位(就是必须是8位的二进制)
stringBuilder.append(byteToString(!flag,huffmanZipBytes[i]));
}
List decodeList = new ArrayList<>();
for (int i = 0; i < stringBuilder.length();) {
int count =1;
Byte b = null;
while (true) {
b = decodeMap.get(stringBuilder.substring(i, i + count));
// if (i+count < stringBuilder.length()) {
// }else {
// b = decodeMap.get(stringBuilder.substring(i));
// }
if (b == null) {
count++;
}else {
decodeList.add(b);
break;
}
}
i+=count;//字符串的下标后移
}
byte[] b = new byte[decodeList.size()];
for (int i = 0; i < decodeList.size(); i++) {
b[i] = decodeList.get(i);
}
return b;
}
/**
* 字节对应的字符串
* @param flag 是否需要补高位
* @param b
* @return
*/
private String byteToString(boolean flag, byte b) {
int tmp = b;
if (flag) {
tmp |= 256;
}
String binaryString = Integer.toBinaryString(tmp);
if (flag) {
return binaryString.substring(binaryString.length() - 8);
}
return binaryString;
}
// 获取哈夫曼压缩后的字节数组
public byte[] getHuffmanZipBytes(byte[] bytes) {
if (bytes == null) {
return null;
}
// 获取节点的集合
List- nodes = this.getNodes(bytes);
// 创建哈夫曼树
Item huffmanTreeRoot = this.creatHuffmanTree(nodes);
// huffmanTreeRoot.preOrder();
// 创建哈夫曼编码
Map
huffmanCodes = this.getCodes(huffmanTreeRoot);
// 根据生成的哈夫曼编码,得到对应的压缩后的字节数组
return this.zip(bytes,huffmanCodes);
}
/**
* 将原始数据进行压缩
* @param bytes 原始数据的byte数组
* @param huffmanCodes 哈夫曼编码表K:原始数据,V:哈夫曼编码(01组成的字符串)
* @return 压缩后的数组
*/
public byte[] zip(byte[] bytes, Map huffmanCodes) {
// 遍历原始数据得到原始数据对应的哈夫曼编码字符串
StringBuilder codeStr = new StringBuilder();
if (bytes==null || huffmanCodes == null) {
return null;
}
for (byte b : bytes) {
if (huffmanCodes.get(b) == null) {
System.out.println("哈夫曼编码表有误!");
return null;
}
codeStr.append(huffmanCodes.get(b));
}
// 将拼接的哈夫曼编码字符串转成byte数组返回(就是每8位是一个byte)
byte[] rst = new byte[(codeStr.length() + 7) / 8];
int rstArrIndex=0;// 返回的byte数组的下标
for (int i = 0; i < codeStr.length(); i+=8) {
if (i + 8 < codeStr.length()) {
rst[rstArrIndex++] = (byte) Integer.parseInt(codeStr.substring(i, i + 8), 2);
}else {
// 最后的一段字符串
rst[rstArrIndex++] = (byte) Integer.parseInt(codeStr.substring(i), 2);
}
}
return rst;
}
/**
* 将ba
* @param flag
* @param codeStr
* @return
*/
// public byte getByte(boolean flag,String codeStr) {
//
// }
/**
* 创建哈夫曼编码
* @param huffmanTreeRoot 哈夫曼树的根节点
* @return k:节点的数据, v: 数据对应的前缀二进制编码字符串
*/
public Map getCodes(Item huffmanTreeRoot) {
this.getCodes(huffmanTreeRoot.left,"0",stringBuilder);
this.getCodes(huffmanTreeRoot.right,"1",stringBuilder);
return this.huffmanCodes;
}
/**
*
* @param node 节点
* @param code 编码. 左: 0,右:1
* @param path 哈夫曼树的路径也就是最后的叶子节点对应的哈夫曼编码
*/
public void getCodes(Item node,String code,StringBuilder path) {
// 定义零时变量用于拼接路径
StringBuilder tmpPath = new StringBuilder(path);
tmpPath.append(code);
if (node != null) {
if (node.data == null) {// 非叶子节点
getCodes(node.left, "0", tmpPath);//左递归
getCodes(node.right, "1", tmpPath);//右递归
}else {
// 将叶子节点对应的数据和编码保存到map中
this.huffmanCodes.put(node.data, tmpPath.toString());
}
}
}
// 创建Huffman树
public Item creatHuffmanTree(List- nodes) {
if (nodes== null || nodes.size() == 0) {
return null;
}
while (nodes.size() > 1) {
// 根据节点的权值(数据出现的次数)从小到大排序
Collections.sort(nodes);
// 取出权值最小的两个元素
Item leftNode = nodes.get(0);
Item rightNode = nodes.get(1);
// 将取出的两个节点构成一个新的非叶子节点
Item parent = new Item();
parent.left = leftNode;
parent.right = rightNode;
parent.weight = leftNode.weight+rightNode.weight;
// 从节点集合中删除之前取出的两个权值最小的节点
nodes.remove(1);// 此处一定要先移除下标为1的然后再移除下标0的
nodes.remove(0);
// 将创建好的新的节点加入到集合中
nodes.add(parent);
}
return nodes.get(0);// 返回哈夫曼树的根节点
}
// 获取节点的集合
public List
- getNodes(byte[] bytes) {
if (bytes == null || bytes.length ==0) {
return Collections.emptyList();
}
//统计数据出现的次数
Map
countsMap = new HashMap<>();
for (byte b : bytes) {
Integer count = countsMap.get(b);// 数据出现的次数
if (count == null) {
countsMap.put(b, 1);
}else {
countsMap.put(b, ++count);
}
}
List- rst = new ArrayList<>(countsMap.size());
for (Map.Entry
entry : countsMap.entrySet()) {
Item node = new Item(entry.getKey(), entry.getValue());
rst.add(node);
}
return rst;
}
}
// 创建节点类
class Item implements Comparable- {
Byte data;// 保存数据
int weight;//权值,就是数据出现的次数
Item left;
Item right;
public Item() {
}
public Item(byte data, int weight) {
this.data = data;
this.weight = weight;
}
public byte getData() {
return data;
}
public void setData(byte data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Item getLeft() {
return left;
}
public void setLeft(Item left) {
this.left = left;
}
public Item getRight() {
return right;
}
public void setRight(Item right) {
this.right = right;
}
@Override
public String toString() {
return "Item{" +
"data=" + data +
", weight=" + weight +
'}';
}
//前序遍历
public void preOrder() {
System.out.println(this);
if (this.getLeft() != null) {
this.getLeft().preOrder();
}
if (this.getRight() != null) {
this.getRight().preOrder();
}
}
@Override
public int compareTo(Item o) {
return this.weight - o.weight;
}
}
哈夫曼编码压缩文件注意事项: