下方链接为用 java 实现哈夫曼树:
https://blog.csdn.net/www_chinese_com/article/details/88070625
目录
一、压缩
二、解压
利用哈夫曼编码对文件进行压缩和解压的大概步骤如下
(1)读取文档中的所有字符,在长度为256的int型数组(以下取名为 ascii)中记录相应字符出现的次数,下标为字符的ASCII码值
(2)将数组中值不为0的项构成二叉树(权值为该项的数值,字符为该项下标ASCII码值对应的字符),将二叉树存储在结点数组中
(3)将结点数字中的结点进行排序并创建一棵哈夫曼树
(4)为了能快速找到相应字符所对应的哈夫曼编码,创建一个哈希表,key 值为 ascii 数组中值不为0的为项的下标,value 为该 key 值字符的哈夫曼编码。
(5)根据文件中的字符串提取出每一个字符,在哈希表中找到对应编码,将编码以 String 类型进行储存。
(6)将解压时需要用到的数据存进压缩文件中
(7)将得到的编码,每 32 个字符为一组,将其装换为 int 型数据存入压缩文件中。如果得到的编码字符数不是 32 的倍数,可以在编码的全面或后面补 0 ,再进行上述操作,不过要将补 0 个数写进文件中。
TreeNode root;// 树的头结点
File inputFile;// 要进行压缩的文件
File outputFile;// 压缩成的文件
String source = "";// 文件中所有的字符,用于编码
String allCode = "";// 记录文件中全部字符的编码
int[] ascii = new int[256];// 存放str中各个字符出现的次数(除中文外)
ArrayList array = new ArrayList();// 将存在的字符的树节点存在array中,用于排序
HashMap map = new HashMap();// 存储字符及其哈夫曼编码
/**
* 构建一个Haffuman树
*
* @param str
* 用于构建Huffman树的数据
*/
public Huffman(File file) {
this.inputFile = file;
collect();// 记录str中字符的出现次序
setArrayList();// 将其存入链表中
sort();// 将其进行排序
// 将链表中的结点
TreeNode tNode = null;
while (array.size() > 1) { // 当链表中的结点数大于1个的时候,将结点不断的加入到哈夫曼树中
tNode = product(array.get(0), array.get(1));
array.remove(0);// 移除原本链表中最小的两个结点
array.remove(0);
array.add(0, tNode);
sort();// 再将新的链表进行排序
}
root = tNode;
setHashMap(root, ""); //(4)
getAllCode(); //(5)
saveCode(); // (6) (7)
}
分解步骤:
(1)读取文档中的所有字符,在长度为256的int型数组(以下取名为 ascii)中记录相应字符出现的次数,下标为字符的ASCII码值
/**
* 堆str中的字符遍历,ascii存放各个字符出现的次数
*
* @param str
* 用于构建Huffman树的数据
*/
public void collect() {
Reader reader;
try {
reader = new FileReader(inputFile);
int n = reader.read();// 读取文件中的字符
while (n != -1) {
char c = (char) n;// 得到文件中字符的ASCII值
source = source + c;
// System.out.println("压缩类collect中的文件中字符c = " + c + " n = " + n);
ascii[n]++;// 将对应位置的次序加一
n = reader.read();
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
(2)将数组中值不为0的项构成二叉树(权值为该项的数值,字符为该项下标ASCII码值对应的字符),将二叉树存储在结点数组中
/**
* 将ascii中不为0的数放进链表中
*/
public void setArrayList() {
for (int i = 0; i < ascii.length; i++) {// 将ascii数组遍历一遍
if (ascii[i] != 0) { // 当其中的值不为0
TreeNode tNode = new TreeNode(new Node(ascii[i], (char) i + ""));
array.add(tNode);
}
}
}
(3)将结点数字中的结点进行排序并创建一棵哈夫曼树
/**
* 根据权值(链表中存放的数据),将ascii代表的字符从小到大进行排序
*/
public void sort() {
// 认为i前面的数据都是有序的,从第二个数开始遍历,
// 记住开始的位置,记住开始的结点,当遇到第一个比它小的数时,
// 检验此时的位置j是否和开始的位置相同,若相同,则不用变化
// 若不相同,删除上一次的位置,将这个结点插入到现在这个位置
for (int i = 1; i < array.size(); i++) {
int min = i;
int j;
TreeNode tNode = array.get(i);
for (j = i; j > 0; j--) {
TreeNode lastNode = array.get(j - 1);
if (tNode.node.pow > lastNode.node.pow)
break;// 找到第一个比他小的数据时。退出循环
}
min = j;
if (min != i) {// 如果,min值发生变化,即前面有比pow大的数
array.remove(tNode);
array.add(min, tNode);
}
}
}
(4)为了能快速找到相应字符所对应的哈夫曼编码,创建一个哈希表,key 值为 ascii 数组中值不为0的为项的下标,value 为该 key 值字符的哈夫曼编码。
/**
* 得到每一个字符对应的哈夫曼编码,再将编码存储到HashMap中(递归)
*
* @param tNode
* 根结点
* @param code
* 这个结点对应的编码
*/
public void setHashMap(TreeNode tNode, String code) {
if (tNode.left != null) {
setHashMap(tNode.left, code + "0");
}
if (tNode.right != null) {
setHashMap(tNode.right, code + "1");
}
if (tNode.left == null && tNode.right == null)
map.put(tNode.node.c, code);
}
(5)根据文件中的字符串提取出每一个字符,在哈希表中找到对应编码,将编码以 String 类型进行储存。
/**
* 得到文件中全部字符的全部编码
*/
public void getAllCode() {
// 将在Huffman中得到的文件的内容全部翻译为编码
for (int i = 0; i < source.length(); i++) {
String key = source.charAt(i) + "";// 得到每一个字符
String value = map.get(key);// 得到每一个字符的编码
allCode = allCode + value;
}
}
(6、7)储存相关数据到压缩文件中
/**
* 将哈夫曼编码存起来
*/
public void saveCode() {
// 存入字符及字符对应的编码, 存入补零个数,存入所有字符的编码
outputFile = new File("src\\Tree\\SaveInformation");
OutputStream out;
try {
// 创建输出流对象
out = new FileOutputStream(outputFile);
DataOutputStream dout = new DataOutputStream(out);
// 将键值数写进压缩文件中
int size = map.size();// map中的键值数
dout.writeInt(size);
// 将HashMap写进文件中
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {// 得到map中的内容
String key = (String) iterator.next();
String value = map.get(key);
dout.writeUTF(key);
dout.writeUTF(value);
}
int addZero = 0;// 添加0的个数
if (allCode.length() % 32 != 0) {
addZero = 32 - allCode.length() % 32;
for (int i = 0; i < addZero; i++)// 给得到的编码补零(在前面加上)
allCode = "0" + allCode;
}
// 写入补零个数
dout.writeInt(addZero);
// 每32位为一个int进行输入
for (int i = 0; i < allCode.length(); i += 32) {
char[] dst = new char[32];
allCode.getChars(i, i + 32, dst, 0);// 得到32个字符形成的字符数组 (用这个方法不可以,出错了)
// for (int j = 0; j < 32; j++) { //依次取出32位编码
// dst[j] = allCode.charAt(i + j);
// }
// 对字符数组进行处理,算出int值,写入文件
// int型第0位为符号位,为避免出错,从第1位开始
int intCode = 0;
for (int j = 1; j < 32; j++) {
intCode = intCode * 2;
intCode = intCode + (dst[j] - '0');
}
//对0号位上的字符进行判断,决定int型数据的正负
if (dst[0] == '1')
intCode = -intCode;
dout.writeInt(intCode);
}
dout.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
由于在压缩是利用数据流将数据存进文件中,因此同样的,用数据流将数据从压缩文件中读取出来,再将编码翻译成字符串即可。
File sourceFile;// 需要解压的文件
File objectFile;// 目标文件
DataInputStream din;// 输入流
Writer writer;// 字符输出流
// 读取文件中HashMap中的内容,得到字符及其相应的编码,但是key值为编码,value值为字符
HashMap map = new HashMap();
public Decompress(File sourceFile) {
this.sourceFile = sourceFile;// 得到目标文件
try {
din = new DataInputStream(new FileInputStream(sourceFile));// 实例化输入流对象
objectFile = new File("src\\Tree\\DecompressingFile");// 构建解压的目标文件
writer = new FileWriter(objectFile);// 实例化输出流对象
operate(); //进行解压的操作方法
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
按存入的顺序将数据读取出来,在这里,我们同样需要将哈希表读取出来并创建一张新的哈希表,但是新创建的这张哈希表,key 值为字符的编码,value 值为字符,因为此时我们是通过编码找到字符。
/**
* 得到HashMap表
*/
public void getHashMap() {
try {
int size = din.readInt();// 得到map的键值数
for (int i = 0; i < size; i++) {
String value = din.readUTF();// 得到字符
String key = din.readUTF();// 得到字符对应的编码
map.put(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
将读取出来的 int 数据转变为二进制的方法
public int[] change(int num) {
int[] array = new int[32];// 将byte化为二进制后的结果存入这个数组中
if (num < 0)
array[0] = 1;
else
array[0] = 0;
num = Math.abs(num);// 得到正数
for (int i = 31; i > 0; i--) {
array[i] = num % 2;
num = num / 2;
}
return array;
}
进行解压的相关操作的方法