Java实现Huffman编码与译码

一、实验目的

通过实验掌握从文件中读取数据及将运行结果保存到文件中的方法,了解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编码
r114130000;594110000
p1123000100=6121100010
:5810001010}12521100011
{12320001011I73111001000
\n10700011[91111001001
\r13700100]93111001010
s115700101f102111001011
y121700110B665110011
g103800111b981011010
e101160100S835110110
(40801010.465110111
)41801011h104111100000
a974011000w119111100001
c994011001d10031110001
l1084011010o1116111001
u1174011011i1051211101
n110170111t116241111
空格326610
(2)使用Huffman编码的压缩率

目标文件的大小为270字节,Huffman编码后的文件为152字节,文件压缩率43.7%

源码

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值