哈夫曼编/译码器
前言
- 这是我上学期数据结构花了两天写的东西,有些地方写的比较仓促
- 但是功能的实现是完整的,并且有良好的界面
- 可以压缩图片音乐等等.
- 且压缩完了变成一个压缩文件随时可以解压
- 今天时间挺多的就整理一下发出来 供大家借鉴
- 并且使用的java版本 但是没有使用什么高难度的东西, 也都是很基础的东西
- 然后界面是用工具生成的代码 那些代码你可以忽视了
界面与流程
文件目录结构(很简单)
- 就只有这么几个文件 第一个是图片是背景图案
- 这个没有放到GitHub中 我就把东西放在百度网盘中
- 百度盘里有两个jar包是皮肤包, 我忘记用的哪个了 当然不下载引入也可以 只是没有黑色的界面颜色
- 链接:https://pan.baidu.com/s/1cQfWGi3JBC94TdpsCVdSDg
提取码:nc4k
首先开始的画面是这里
在C盘下的图片里面有这样一张图片 48KB需要去压缩存储
这里我们需要压缩文件, 不可以点解压缩 如下面
点击选择文件
这个使用文件名就出现了
点击右下角的编码保存
然后就保存在了 D盘的 huffman文件夹下
保存在这里 而且后面是.huffmanZIP结尾的 不能破坏这个后缀名 否则无法解压缩
当然点击上面的二进制码预览 还可以看看二进制码 只要你看的进去
这样子我们就把48KB的图片压缩成了19KB
然后打开一个新的窗口
找到D盘huffman下的
点击解压缩 然后点击译码保存
如果不勾选解压缩,点击译码保存的话就会出现下图
而且有点小bug 不过不影响就是这样做完 你要重新选择一下文件
如果选择的文件后缀不是以.huffmanZIP结尾的 会出现下图
好了 译码保存
保存完成后打开D盘的huffman
大概的功能就演示到这里
源代码讲解
首先建立Huffman树的代码
我把代码的讲解放在代码的注释里了
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
public class HufTree {
//ASCII码 256个
private static final int LEN = 256;
//统计字节个数
private int[] byteCount = new int[LEN];
//每个字节的huffman编码
private String[] charCode = new String[LEN];
private String text;
//最小优先队列, 也就是构建huffman的时候可以找出最小的堆顶
//优先队列可以参看我的另一篇博客https://blog.youkuaiyun.com/qq_42011541/article/details/80678405
//看个思想就可以了
private PriorityQueue<hufNode> nodeQueue = new PriorityQueue<>(LEN, new Comparator<hufNode>() {
@Override
public int compare(hufNode o1, hufNode o2) {
return o1.count - o2.count;
}
});
//huffman树的结点 左孩子 右孩子 编码 个数
private static class hufNode {
private hufNode lchild;
private hufNode rchild;
private String str;
private int count;
public hufNode(String str, int count) {
super();
this.str = str;
this.count = count;
}
}
public HufTree(File from, File to) {
compress(from, to);
}
private void compress(File file, File file2) {
byte[] bs = readFile(file);// 读取文件
countChar(bs);// 统计词频
hufNode root = createTree();// 创建哈夫曼树
generateCode(root, "");// 生成哈夫曼编码
writeFile(bs, file2);// 写压缩文件
}
//读取文件 并且放在整个字节数组里
//这个我是一次性读取了, 因为只是课设没有分组读取,一次性读取可能占得内存太大了
private byte[] readFile(File file) {
byte[] bs = new byte[(int) file.length()];
BufferedInputStream bis = null;
try {
String s;
bis = new BufferedInputStream(new FileInputStream(file));
bis.read(bs);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bs;
}
//统计个数
private void countChar(byte[] bs) {
for (int i = 0, length = bs.length; i < length; i++) {\
//这里可以判断汉字的部分 < 0代表出现特殊编码了
if (bs[i] < 0) {
byteCount[LEN + bs[i]]++;
} else {
byteCount[bs[i]]++;
}
}
}
// 创建哈夫曼树
private hufNode createTree() {
for (int i = 0; i < LEN; i++) {
if (byteCount[i] != 0) {// 使用优先队列保存
nodeQueue.add(new hufNode((char) i + "", byteCount[i]));
}
}
while (nodeQueue.size() > 1) {
hufNode min1 = nodeQueue.poll();// 获取并移除最小堆顶
hufNode min2 = nodeQueue.poll();
hufNode result = new hufNode(min1.str + min2.str, min1.count + min2.count);
result.lchild = min1;
result.rchild = min2;
nodeQueue.add(result);// 加入左节点
}
return nodeQueue.peek();// 返回根节点
}
/**
每个结点的Huffman编码
*/
private void generateCode(hufNode root, String s) {
if (root.lchild == null && root.rchild == null) {
// 保存至编码数组对应位置
charCode[(int) root.str.charAt(0)] = s;
}
if (root.lchild != null) {// 左边加0
generateCode(root.lchild, s + "0");
}
if (root.rchild != null) {// 右边加1
generateCode(root.rchild, s + "1");
}
}
// 写入压缩文件 :1、各字节编码长度 2、各字节编码 3、编码后的文件
private void writeFile(byte[] bs, File file2) {
BufferedOutputStream bos = null;// 声明字符缓冲流
try {
// 创建字符缓冲流
bos = new BufferedOutputStream(new FileOutputStream(file2));
// 写入各字节编码长度,并获得编码的二进制文件
String binaryCode = writeCodeLength(file2, bos);
// 字节数组文件转码成二进制文件
String binaryFile = transFile(bs);
text = binaryFile;
// 合并二进制编码及文件(二进制编码+二进制文件)
String codeAndFile = binaryCode + binaryFile;
// 生成字节并 写入合并后二进制文件文件
generateFile(bos, codeAndFile);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 写入各字节编码长度
* 并且返回这段编码
* @param file2
* @param bos
* @return
* @throws IOException
*/
private String writeCodeLength(File file2, BufferedOutputStream bos) throws IOException {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < LEN; i++) {
if (charCode[i] == null) {
bos.write(0);
bos.flush();
} else {
sb.append(charCode[i]);
bos.write(charCode[i].length());
bos.flush();
}
}
return sb.toString();
}
//转码
private String transFile(byte[] bs) {
StringBuilder sb = new StringBuilder();
for (int i = 0, length = bs.length; i < length; i++) {
//汉字判断处理部分
if (bs[i] < 0) {
sb.append(charCode[LEN+ bs[i]]);
} else {
sb.append(charCode[bs[i]]);
}
}
return sb.toString();
}
// 生成字节文件
private void generateFile(BufferedOutputStream bos, String codeAndFile) throws IOException {
int lastZero = 8 - codeAndFile.length() % 8;
int len = codeAndFile.length() / 8 + 1;
if (lastZero != 0) {
len =