前言
不了解哈夫曼树的可以移步查看我的另一篇博客:哈夫曼树(最优二叉树)
使用哈夫曼编码压缩文件,其实就是将每个字符的哈夫曼编码得到,每8位转为一个字节存到文件中,解压缩的时候,在将字节转为二进制重新找到哈夫曼编码对应的字符,这样即可完成文件的解压缩。
文件解压缩的方法:
①将每个字符对应的权值存入压缩文件,在解压时重写构建哈夫曼树,遍历哈夫曼树来获得对应的字符
②将每个字符对应的哈夫曼编码以及长度存入压缩文件,在解压时根据每个字符对应哈夫曼编码的长度,来截取每个字符对应的哈夫曼编码
本博客使用:方法②。
方法①:用于在使用字节流传输时如果每个字符对应的权值大于255时,就会出现权值错误,这是由于java在字节流传输时,会将int转为bety,取int低8位,而int为32位,那么大于8位的数值就会丢失。
具体参考该博客
当然可以使用字符流来传输就可以解决这个问题。
一、文件压缩
大体步骤:
- 读取文件,统计每个字符出现的次数(权值)
- 根据权值,创建哈夫曼树
- 遍历哈夫曼树,得到每个字符的哈夫曼编码
- 再次读取文件,将每个字符对应的哈夫曼编码拼接,每8位编码转为一个字节写入压缩文件
注意:
- 字符可能出现特殊字符,btye值小于0,需要特殊处理,代码中有
- 需要将码表(每个字符对应的长度、字符对应的哈夫曼编码)写入压缩文件,用于文件解压
- 每8位转一个字节,如果不够那么就需要补0,所以需要将最后8位补0的个数写入文件
- 使用缓存机制,减少io次数,提高效率
Compress类
Compress.java
package com.kiger.fileDecompression;
import java.io.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* @ClassName Compress
* @Description 压缩文件类
* @Author zk_kiger
* @Date 2019/11/7 18:55
* @Version 1.0
*/
public class Compress {
static final int CHAR_INDEX = 256;
static final int BUFFER_SIZE = 128;
// 用来记录文件中字符出现的次数,下标对应字符的ASCII码
private int[] times = new int[CHAR_INDEX];
// 用来记录每个字符对应的huffman编码
private String[] huffmanCodes = new String[CHAR_INDEX];
// 优先队列用于创建huffman树,自动从小到大排序结点
private PriorityQueue<Node> queue = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.getWeight() - o2.getWeight();
}
});
public Compress() {
for (int i = 0; i < huffmanCodes.length; i++) {
huffmanCodes[i] = "";
}
}
/**
* 压缩文件
* @param fromPath 被压缩文件路径
* @param toPath 已压缩文件路径
*/
public void compress(String fromPath, String toPath) {
compress_(fromPath, toPath);
}
private void compress_(String fromPath, String toPath) {
// 1.读取文件并统计字符权值
statCharWeight(fromPath);
// 2.根据权值创建Huffman树
Node root = createHuffman();
// 3.根据前序遍历获得编码表
getHuffmanCode(root, "");
System.out.println("正在压缩文件...");
// 4.根据编码表压缩文件
compressFile(fromPath, toPath);
System.out.println("文件压缩完成...");
}
// 根据编码表压缩文件
byte value = 0;
int index = 0;
int writeBufferSize = 0;
byte[] writeBuffer = new byte[BUFFER_SIZE];
int lastIndex = 0; // 最后一个字节补0的个数
private void compressFile(String fromPath, String toPath) {
File toFile = new File(toPath);
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fromPath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(toFile))
) {
// 将每个编码的长度写入文件
StringBuilder code = new StringBuilder();
for (int i = 0; i < CHAR_INDEX; i++) {
bos.write(huffmanCodes[i].length());
// if (huffmanCodes[i].length() != 0)
// System.out.println(i + " : " + huffmanCodes[i]);
code.append(huffmanCodes[i]);
}
// 再将哈夫曼编码写入文件
char[] charArray = code.toString().toCharArray();
for (int i = 0; i < charArray.length; i++) {
if (charArray[i] == '0')
value = CLR_BYTE(value, index);
if (charArray[i] == '1')
value = SET_BYTE(value, index