哈弗曼压缩
对于初学哈弗曼压缩,技术不是十分娴熟,按我自己的方法将之分为以下7个大步骤:
一. 先定义节点属性方法
(0). 定义节点的属性和方法
在TreeNode节点类中定义了 左节点(left),右节点(right),父节点(last),数据内容(obj),频率
(num),编码(src)属性;定义了每个属性对应的get,set的方法。
private TreeNode left;// 左节点
private TreeNode right;// 右节点
private TreeNode last;// 父节点
private String src = "0";// 数据对应的哈弗曼编码
private byte obj;// 数据
private int num;// 出现的频率
二. 将源文件数据按字节读取(共256个字节)。
(1). 获取文件路径。 (path是String类型的)
// 创建一个文件选择器
javax.swing.JFileChooser ch = new javax.swing.JFileChooser();
// 设置只能选择文件
ch.setFileSelectionMode(javax.swing.JFileChooser.FILES_ONLY);
int t = ch.showOpenDialog(null);
if (t == 0) {
// 获得要压缩的文件的路径
path = ch.getSelectedFile().getAbsolutePath();
}
(2). 获得文件大小。
java.io.File file = new java.io.File(path);
long num = file.length();
(3). 读入数据并统计
三. 统计每个字节出现的频率。
(4). 通过路径,来统计文件中每个字节出现的频率。
先构建一个节点队列,然后将读取的一个字节当做节点的数据转化成一个节点,与队列中的元素进行
比较,如果原队列中未曾出现这个节点,就将之添加到队列中,反之则节点频率+1。返回一个节点队列(qi)。
public QueueImg<TreeNode> tongji(String path) throws IOException {
// 创建一个节点队列
QueueImg<TreeNode> qi = new QueueImg<TreeNode>();
// 读入数据
java.io.InputStream ins = new java.io.FileInputStream(path);
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(ins);
java.io.DataInputStream dis = new java.io.DataInputStream(bis);
// 读入数据,如果不是结尾继续往下读
java.io.File file = new java.io.File(path);
long num = file.length();
for (long i = 0; i < num; i++) {
byte x = dis.readByte();
// 将字节转化为节点
TreeNode node = new TreeNode(x);
// 当队列中不只一个元素时
for (int j = 0; j <= qi.size(); j++) {
// 如果队列中没有对应节点
if (j == qi.size()) {
node.setNum(1);
qi.add(node);
break;
}
// 如果队列中有对应节点
else if (qi.get(j).getObj() == node.getObj()) {
qi.get(j).setNum(qi.get(j).getNum() + 1);
break;
}
}
}
ins.close();
return qi;
}
四. 根据频率构建哈弗曼树,在每个节点上注明哈弗曼编码。
(5). 建树
传入一个节点队列,将其中的节点按频率大小,选出最小的节点(a),再从队列中移除,再选出一次(b),再
移除。将这两个节点构建成新的节点。返回根节点(root)。
public TreeNode createTree(QueueImg<TreeNode> qi) {
TreeNode a, b, c;
// 当队列中有两个或两个以上的元素时
while (qi.size() > 1) {
// 取最小的节点
a = min(qi);
qi.remove(a);
b = min(qi);
qi.remove(b);
c = new TreeNode((byte) 0);
build(c, b, a);
qi.add(c);
}
TreeNode root = qi.get(0);
return root;
}
(6). 建码
传入一个根节点,按左1右0的方式编码。再将所需节点放入队列(queue)中备用。
public void createBM(TreeNode root) {
// 左边为1
if (root.getLeft() != null) {
root.getLeft().setSrc(root.getSrc() + 1);
createBM(root.getLeft());
}
// 右边为0
if (root.getRight() != null) {
root.getRight().setSrc(root.getSrc() + 0);
createBM(root.getRight());
} else
queue.add(root);
}
五. 将原来的文件数据,按哈弗曼编码的形式翻译成字符串。
(7). 通过与队列(queue)中的元素比较,相同则输出其编码。
java.io.File file = new java.io.File(path);
long num = file.length();
String src = "";
for (long i = 0; i < num; i++) {
byte x = dis.readByte();
for (int j = 0; j < queue.size(); j++) {
if (x == queue.get(j).getObj())
src = src + queue.get(j).getSrc();
}
}
六. 以8个位为一个字节,组成新的数据,再写入。
(8). 先写入对应法则;再将原数据补齐至8的整数倍(后尾补0),写入补齐数(skip);最后写入新数据本身。
这里用了substring(int beginIndex, int endIndex) 的方法来切割字符串,beginIndex是开始的索引处
(包括),endIndex是结束的索引处(不包括)。再用parseInt(String s, int radix) 方法,radix是基数。
这里是2进制转10进制,所以是2.
// 写入对应法则
for (int i = 0; i < queue.size(); i++) {
// 数据
dos.write(queue.get(i).getObj());
// 哈弗曼编码
dos.write(Integer.parseInt(queue.get(i).getSrc(), 2));
}
// 补齐位数
byte skip = 0;
if (src.length() % 8 != 0) {
skip = (byte) (8 - src.length() % 8);
for (int i = 0; i < skip; i++) {
src = src + 0;
}
}
// 补齐了skip个
dos.write(skip);
// 将字符串转化为字节
for (int i = 0; i < src.length() / 8; i++) {
String src1 = src.substring(0 + 8 * i, 8 + 8 * i);
int b = Integer.parseInt(src1, 2);
dos.write(b);
}
七. 总结
因为是初学java,还有许多不足之处,比如说在编写时,思路不够清晰,结构混乱,数据冗杂,数据利用率不高。与此同时在遇到了许多难点,比如 如何在建好的树上添加对应的编码?如何将字符串转化为整型?如何加快压缩的速度?其中大部分已经解决,其他的也用较为复杂的方法实现。附件中还有解压缩的部分。请多指教。。。