一、实验目的
通过实验掌握从文件中读取数据及将运行结果保存到文件中的方法,了解Huffman编码的算法设计及使用Java实现的方法。
二、实验内容
对文件Input.txt中的字符使用Huffman编码进行编码,将编码结果保存到文件Output.txt文件中,最后对Output.txt文件中的字符进行译码。
程序要先统计文件中字符的种类数,每种字符的个数,然后通过Huffman算法计算出各字符的Huffman编码,使用该编码对文件进行编码,并将结果保存到Output.txt文件中。最后对Output.txt文件中的字符进行译码,将译码结果保存到文件Output1.txt中。
三、实验要求
1.文件Output.txt大小应比文件Input.txt文件的小。
2.译码结果应和原文件input.txt中的内容一致。
四、相关算法及程序说明
(1)统计文件中字符的频率
先读取文件,创建一个256大小的数组b保存字符出现的频率,下标对应该字符的ASCII码,值为该字符出现的频率,通过循环遍历读取文件内容,统计各个字符出现的频率;创建一个HashMap来保存该文件字符及其对应的频率,key值为字符的ASCII码,value值为频率,通过遍历数组b,如果数组元素不等0,则将该元素的下标存到HashMap的key值,对应的value为该元素的值,最后返回该HashMap。
public static HashMap<Integer, Integer> getCharWeight(String
inputFile) throws IOException {
FileInputStream inputStream = new FileInputStream(inputFile);
HashMap<Integer, Integer> hashMap = new HashMap<>();
int[] b = new int[256];
int read;
while ((read=inputStream.read())!=-1){
b[read]+=1;
}
for (int i = 0; i < b.length; i++) {
if (b[i]!=0){
hashMap.put(i,b[i]);
}
}
return hashMap;
}
(2)创建huffman树结点类
该节点类的属性包含权重(weight)、ASCII码(asc)、树的左孩子(lchild)和树的右孩子(rchild),一个全参构造方法。
class Node{
int weight; //权重
int asc; //该的字符ACll码
Node lchild; //左孩子
Node rchild; //右孩子
//构造方法
public Node(int weight, int asc, Node lchild, Node rchild) {
this.weight = weight;
this.asc = asc;
this.lchild = lchild;
this.rchild = rchild;
}
}
(3)创建huffman树
先创建一个ArrayList来保存叶子结点,将上面已保存的每个字符及其对应的频率添加到ArrayList当中;对列表中的元素根据结点的权重来按小到大进行排序,取出排序后列表中的权重最小和次小的结点,将该两个结点的权重相加的值作为该两个结点的父结点,左孩子为最小权重的结点,右孩子为次小权重的结点,将该父结点加入列表中,并移除最小和次小的结点;重复以上该过程,直到列表中只剩一个结点时,则退出循环;最后,返回该结点,即为huffman树的根结点。
public static Node createTree(HashMap<Integer, Integer> charWeight){
//创建保存叶子结点的列表
ArrayList<Node> tree = new ArrayList<>();
charWeight.forEach((k,v)->{
tree.add(new Node(v,k,null,null));
});
//构造Huffman树
while (tree.size()!=1){ //当列表中只剩下一个结点的时候则退出循环
//根据树的权重进行排序
Collections.sort(tree, Comparator.comparingInt(o -> o.weight));
//将排序后的列表中取前面两个最小的树的结点进行合并
//权重为该两个结点的权重和,左孩子为最小权重的结点,右孩子为次小权重的结点
//重新构造一颗树结点,将该结点加到原来的列表当中
//然后,将最小的两个结点从列表中移除
tree.add(new Node(tree.get(0).weight+tree.get(1).weight,-1,tree.get(0),tree.get(1)));
tree.remove(tree.get(0));
tree.remove(tree.get(0));
}
//返回列表中的第一个结点,即为该Huffman树的根结点
return tree.get(0);
}
(4)先序遍历huffman树
先遍历huffman树的根结点,再遍历树的左孩子,每遍历一个左孩子结点,在code字符串后加“0”,直到左孩子为null时,则返回继续遍历右孩子,每遍历一个右孩子结点,在code字符串后加“1”,右孩子为null时,则huffman树遍历完成。最后,将各个字符ASCII码及其对应的huffman编码存到HashMap当中。(huffmanCode和huffmanCode2为全局HashMap变量,其中huffmanCode的key为huffman编码,value为ASCII码,huffmanCode2的key为ASCII码,value为huffman编码)
public static void printHuffmanCode(Node root, String code) {
if(root!=null) {
if(root.lchild==null&&root.rchild==null) {
huffmanCode.put(code,root.asc);
huffmanCode2.put(root.asc,code);
}
printHuffmanCode(root.lchild,code+"0");
printHuffmanCode(root.rchild,code+"1");
}
}
(5)对文件进行编码
先读取文件,通过循环遍历文件中的每一个字符,寻找该字符的ASCII码在huffmanCode2中的key值所对应的huffman编码,将该huffman编码加到编码字符串中,当编码字符串的长度大于等于31的时候,截取该编码字符串的前31位01字符串转为十进制,将转为后的十进制数字写入文件中,再删除编码字符串前31位的01字符串,如果最后一段01字符串不足31位,则将剩下的部分转为十进制。最后,将编码后的字符串写入文件中。
public static void coding(String inputFile, String outputFile)
throwsIOException {
//编码文件
FileInputStream inputStream = new FileInputStream(inputFile);
//保存编码后的文件
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(outputFile));
int read;
//编码后的字符串
StringBuilder codingContent = new StringBuilder();
int n=31;//01字符串转为十进制的位数
int num;//十进制数
while ((read=inputStream.read())!=-1){
String s = *huffmanCode2*.get(read);//获取该ASCII码对应的huffman编码
codingContent.append(s);//将huffman加入到codingContent中
if (codingContent.length()>=n){
num = Integer.*valueOf*(codingContent.substring(0,n),2);// 截取31位01字符串转为十进制
outputStream.writeInt(num);//将十进制数字写入文件
codingContent.delete(0,n);//删除已经转为十进制的01字符串
}
}
//处理最后一个不足31位的01字符串
num = Integer.*valueOf*(codingContent.substring(0,codingContent.length()),2);
outputStream.writeInt(num);
*lastNum* = num;//最后一个不足31位的01字符串转为十进制的数字
*lastLen* = codingContent.length();//最后一个不足31位的01字符串的长度
outputStream.close();
}
(6)对文件进行解码
先读取解码文件,读取文件中的十进制数字,将十进制转为二进制,如果转换后的二进制位数不等于31位,则在该二进制前面添加0,直到二进制位数等于31位为止,对于最后一个二进制数要与前面最后保存的01字符串的长度进行比较,若两者不等则在该二进制前面添加0,直到两者长度相等为止。最后,将转换后的二进制01字符串保存到编码字符串中,通过循环遍历编码字符串中的每一个字符,每次循环将该字符加入临时字符串中,如果huffmanCode的key包含该临时字符串,则将该临时字符串所对应的字符进行解码,将解码后的字符加入到解码后的字符串中,接着删除临时字符串,将它置为空,重复进行上述操作,直到遍历结束。最后,将解码后的字符串写入文件中。
public static void decoding(String inputFile, String outputFile)
throws
IOException {
DataInputStream dis = new DataInputStream(new FileInputStream(inputFile));
int a;//文件读取的十进制数字
int l;//十进制数字转为二进制后的长度与31位的差值
StringBuilder coding = new StringBuilder();//编码字符串
StringBuilder s;// 保存二进制的字符串
while (dis.available()>0){
a = dis.readInt();
s = new StringBuilder(Integer.*toBinaryString*(a));//将读取的十进制数转为二进制
//若十进制数字转为二进制后的长度不等于31,则在该二进制数前面补充l个0
if (a==*lastNum*){//处理最后一个二进制数
l = *lastLen*-s.length();
for (int i = 0; i < l; i++) {
s.insert(0,"0");
}
}else if ((l=31-s.length())!=0){
for (int i = 0; i < l; i++) {
s.insert(0,"0");
}
}
coding.append(s);//将二进制字符串加入到编码字符串中
}
//临时字符串
StringBuilder str = new StringBuilder();
//解码后的字符串
StringBuilder decodingContent = new StringBuilder();
for (int i=0;i<coding.length();i++) {
str.append(coding.charAt(i));//将字符加入临时字符串中
//如果临时字符串等于已存在的huffman编码,则将该huffman编码解码为对应的字符
if (*huffmanCode*.containsKey(str.toString())) {
String s1 = String.*valueOf*((char) *huffmanCode*.get(str.toString()).intValue());//将ASCII码转为char,再将char转为String
decodingContent.append(s1);//将解码后的字符加到decodingContent字符串中
str.delete(0, str.length());//删除临时字符串
}
}
//解码后的文件
FileOutputStream outputStream = new FileOutputStream(outputFile);
//将解码后的字符串写入到文件中
outputStream.write(decodingContent.toString().getBytes());
outputStream.close();
}
五、结果分析
(1)文件包含的不同字符的个数,每个字符的Huffman编码。
字符 | ASCII | 频率 | Huffman编码 | 字符 | ASCII | 频率 | Huffman编码 |
---|---|---|---|---|---|---|---|
r | 114 | 13 | 0000 | ; | 59 | 4 | 110000 |
p | 112 | 3 | 000100 | = | 61 | 2 | 1100010 |
: | 58 | 1 | 0001010 | } | 125 | 2 | 1100011 |
{ | 123 | 2 | 0001011 | I | 73 | 1 | 11001000 |
\n | 10 | 7 | 00011 | [ | 91 | 1 | 11001001 |
\r | 13 | 7 | 00100 | ] | 93 | 1 | 11001010 |
s | 115 | 7 | 00101 | f | 102 | 1 | 11001011 |
y | 121 | 7 | 00110 | B | 66 | 5 | 110011 |
g | 103 | 8 | 00111 | b | 98 | 10 | 11010 |
e | 101 | 16 | 0100 | S | 83 | 5 | 110110 |
( | 40 | 8 | 01010 | . | 46 | 5 | 110111 |
) | 41 | 8 | 01011 | h | 104 | 1 | 11100000 |
a | 97 | 4 | 011000 | w | 119 | 1 | 11100001 |
c | 99 | 4 | 011001 | d | 100 | 3 | 1110001 |
l | 108 | 4 | 011010 | o | 111 | 6 | 111001 |
u | 117 | 4 | 011011 | i | 105 | 12 | 11101 |
n | 110 | 17 | 0111 | t | 116 | 24 | 1111 |
空格 | 32 | 66 | 10 |
(2)使用Huffman编码的压缩率
目标文件的大小为270字节,Huffman编码后的文件为152字节,文件压缩率43.7%