哈夫曼编码实现对文件的编码解码java语言

一、简单说一下Huffman:

用Java语言写一个用哈夫曼编码对任意一个文件(.txt)的内容进行编解码的程序。

(学了数据结构!懂!)
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最 佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)

简单来说,若在一个字符串中,知道每个字母各自出现的频率,通过将出现频率较大的字符采用较少位数来编码的方式达到压缩的目的,即一个字符出现的频次越大,它的编码位数应该更少。

二、结果、代码、说明:

①在自行建立input.txt文件任意写入内容(中文数字字母等,记得要建在同一个文件夹目录)
②运行后,可以在自行建立的input.txt文件里查看到:
(1)编码前内容、(2)编码表、(3)解码后内容
运行结果
文件夹内容

//哈夫曼编码实现对文件的编码解码
package com;  //建立package为com
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;
import org.junit.Test;
import java.io.*;

public class Huffman {
    //内部类 二叉树节点
    private class TreeNode {
        public TreeNode() { }
        public TreeNode(Character ch, int val, int freq, TreeNode left, TreeNode right) {
            this.ch = ch;
            this.val = val;
            this.freq = freq;
            this.left = left;
            this.right = right;
        }
        Character ch;
        int val;
        int freq;
        TreeNode left;
        TreeNode right;
    }

    @Test
    public void testEncode(){

        /*读取txt文本*/
        String pathname = "input.txt";
        // 绝对路径或相对路径都可以,写入文件时演示相对路径,读取以上路径的input.txt文件
        try (FileReader reader = new FileReader(pathname);
             BufferedReader br = new BufferedReader(reader) // 建立一个对象,
        ) {
            String s = null;// = "aaabbbeeedacfwwwwddd";
            //String line;
            while ((s = br.readLine()) != null) {
                // 一次读入一行数据
                System.out.println("编码前:"+s);
                Object[] encodeRes = encode(s);
                String encodeStr = (String)encodeRes[0];
                Map<Character,String> encodeMap = (Map<Character, String>)encodeRes[1];

                System.out.println("编码表:");
                for(Map.Entry<Character,String> e:encodeMap.entrySet()){
                    System.out.println(e.getKey()+":"+e.getValue());
                }
                System.out.println("编码后:"+encodeStr);

                String decodeStr = decode(encodeStr,encodeMap);
                System.out.println("解码后:"+decodeStr);

                //写入文本
                try {
                    File writeName = new File("output.txt"); // 相对路径,如果没有则要建立一个新的output.txt文件
                    writeName.createNewFile(); // 创建新文件,有同名的文件的话直接覆盖
                    try (FileWriter writer = new FileWriter(writeName);
                         BufferedWriter out = new BufferedWriter(writer)
                    ) {
                        out.write("--------原文本内容--------\r\n"); // \r\n即为换行
                        out.write(s);
                        out.write("\r\n\n");
                        out.write("---------哈夫曼编码输出---------\r\n"); // \r\n即为换行
                        out.write(encodeStr);
                        out.write("\r\n\n");
                        out.write("--------编码表--------\r\n"); // \r\n即为换行
                        for(Map.Entry<Character,String> e:encodeMap.entrySet()){
                            out.write(e.getKey()+":"+e.getValue());
                            out.write("\r\n");
                        }
                        out.write("\r\n\n");
                        out.write("--------解码输出--------\r\n"); // \r\n即为换行
                        out.write(decodeStr);
                        out.write("\r\n\n----2021----");
                        out.flush(); // 把缓存区内容压入文件
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //编码方法,返回Object[],大小为2,Objec[0]为编码后的字符串,Object[1]为编码对应的码表
    public Object[] encode(String s){
        Object[]res= new Object[2];
        Map<Character,String> encodeMap = new HashMap<Character, String>();
        TreeNode tree = constructTree(s);
        findPath(tree, encodeMap, new StringBuilder());
        findPath(tree, encodeMap, new StringBuilder());
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<s.length();i++){
            String tmp = encodeMap.get(s.charAt(i));
            sb.append(tmp);
        }
        res[0]=sb.toString();
        res[1] = encodeMap;
        return res;

    }

    /*
     * 根据字符串建立二叉树
     * @param s:要编码的源字符串
     */
    private TreeNode constructTree(String s) {
        if (s == null || s.equals("")) {
            return null;
        }
        //计算每个字母的词频,放到Map中
        Map<Character, Integer> dataMap = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            Character c = s.charAt(i);
            if (dataMap.containsKey(c)) {
                int count = dataMap.get(c);
                dataMap.put(c, count + 1);
            } else {
                dataMap.put(c, 1);
            }
        }
        //遍历dataMap,初始化二叉树节点,并将所有初始化后的节点放到nodeList中,并进行排序
        LinkedList<TreeNode> nodeList = new LinkedList<TreeNode>();
        for (Map.Entry<Character, Integer> entry : dataMap.entrySet()) {
            Character ch = entry.getKey();
            int freq = entry.getValue();
            int val = 0;
            TreeNode tmp = new TreeNode(ch,val,freq,null,null);
            nodeList.add(tmp);
        }
        //对存放节点的链表进行排序,方便后续进行组合
        Collections.sort(nodeList, new Comparator<TreeNode>() {
            public int compare(TreeNode t1, TreeNode t2) {
                return t1.freq-t2.freq;
            }
        });

        //size==1,代表字符串只包含一种类型的字母
        if(nodeList.size()==1){
            TreeNode t = nodeList.get(0);
            return new TreeNode(null,0,nodeList.get(0).freq,t,null);
        }

        //利用排序好的节点建立二叉树,root为初始化根节点
        TreeNode root = null;
        while(nodeList.size()>0){
            //因为nodeList在前面已经排好序,所以直接取出前两个节点,他们的和肯定为最小
            TreeNode t1 = nodeList.removeFirst();
            TreeNode t2 = nodeList.removeFirst();
            //左子树的val赋值为0,右子树的val赋值为1
            t1.val = 0;
            t2.val = 1;
            //将取出的两个节点进行合并
            if(nodeList.size()==0){
                //此时代表所有节点合并完毕,返回结果
                root = new TreeNode(null,0,t1.freq+t2.freq,t1,t2);
            }else {
                //此时代表还有可以合并的节点
                TreeNode tmp = new TreeNode(null,0,t1.freq+t2.freq,t1,t2);

                //t1、t2合并后,需要将得到的新节点加入到原链表中,继续与其他节点合并,
                //此时需要保证原链表的有序性,需要进行排序
                if(tmp.freq>nodeList.getLast().freq){
                    nodeList.addLast(tmp);
                }else {
                    for(int i=0;i<nodeList.size();i++){
                        int tmpFreq = tmp.freq;
                        if(tmpFreq<= nodeList.get(i).freq){
                            nodeList.add(i,tmp);
                            break;
                        }
                    }
                }
            }
        }
        //返回建立好的二叉树根节点
        return root;
    }

    //对已经建立好的二叉树进行遍历,得到每个字符的编码
    private void findPath(TreeNode root, Map<Character,String> res, StringBuilder path) {
        if (root.left == null && root.right == null) {
            path.append(root.val);
            res.put(root.ch,path.substring(1));
            path.deleteCharAt(path.length() - 1);
            return;
        }
        path.append(root.val);
        if (root.left != null) findPath(root.left, res, path);
        if (root.right != null) findPath(root.right, res, path);
        path.deleteCharAt(path.length() - 1);
    }

    //对字符串进行解码,解码时需要编码码表
    public String decode(String encodeStr,Map<Character,String> encodeMap){
        StringBuilder decodeStr = new StringBuilder();
        while(encodeStr.length()>0){
            for(Map.Entry<Character,String> e: encodeMap.entrySet()){
                String charEncodeStr = e.getValue();
                if(encodeStr.startsWith(charEncodeStr)){
                    decodeStr.append(e.getKey());
                    encodeStr = encodeStr.substring(charEncodeStr.length());
                    break;
                }
            }
        }
        return decodeStr.toString();
    }
}

三、readMe内容:

***readMe:***
①软件:Java:下载安装环境配置教程(可直接下载最新版):
链接: https://blog.csdn.net/qq_45422588/article/details/105640508.
简单测试:直接在cmd运行第一个HelloWorld程序
链接: https://www.cnblogs.com/baoxiaofei/p/4116632.html.

②编辑器:IntelliJ IDEA
链接: https://blog.csdn.net/weixin_44519789/article/details/108023152.
(好像Eclipse更好! 下载链接: https://blog.csdn.net/weixin_43821795/article/details/100525115.)

(为了防止下次自己要用到又忘了,所以码出来,🤫)

小提示:
如果使用eclipse出现文本输出乱码
链接: https://blog.csdn.net/limenghua9112/article/details/50783679.
(或许如果有空也可以详细了解一下这几个家伙UTF-8、ANSI、GBK、GB2312 吧啦吧啦~)

这里是题外话(今天遇到一个超有感触的座右铭,记下来)
贵有恒,何必三更起五更睡;最无益,只怕一日曝十日寒。
努力,是为了将运气成分降到最低。

完结!
  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值