(算法课设)huffman编码 实现文件压缩和解压

如果这样看麻烦的话 点击我主页面 资源那 有这个哈夫曼编码代码的文件免费下载
import java.io.*;
import java.util.*;



class 哈夫曼编码 {
    static int last ;
    static HashMap<Character,String> huffmanTreeMapCode =new HashMap<>(); //哈夫曼编码表
    static StringBuilder stringBuilder =new StringBuilder();//字符串拼接类
    public static void main(String[] args) throws IOException, ClassNotFoundException {


            File file =new File("E:\\java\\Demo.txt"); //创建一个文件


            if(!file.exists()) //如果没有则创建
                file.createNewFile();
            File file1 =new File("E:/java/zipDemo.zip");
            if(!file.exists()) //如果没有则创建
                 file1.createNewFile();
            File file2 =new File("E:/java/unzip.txt");
            if(!file2.exists()) //如果没有则创建
                file.createNewFile();
            String srcFile ="E:/java/Demo.txt"; //原文件路径
            String zipFile ="E:/java/zipDemo.zip";//压缩后的文件路径
            String deZipFile ="E:/java/unzip.txt";//解压之后的文件路径
            zipFile(srcFile,zipFile); //压缩文件
            unZip(zipFile,deZipFile);//解压文件


    }
    public static void unZip(String zipFile,String dstFile) throws IOException, ClassNotFoundException//解压文件
    {
            FileInputStream fis =new FileInputStream(zipFile); //文件输入流
            byte[] bytes =new byte[fis.available()];  //创建一个byte数组大小为fis数据的长度
            fis.read(bytes); //将数据读取到bytes数组

           String s=Decode(bytes,huffmanTreeMapCode);//得到解码的字符串
            FileOutputStream fos =new FileOutputStream(dstFile);//文件输出流
            fos.write(s.getBytes());//将其写入到解压文件中
            fis.close();//关闭输入输出流
            fos.close();//关闭输入输出流
    }
    public static void zipFile(String srcFile,String dstFile) throws IOException //压缩文件
    {
       FileInputStream fis = new FileInputStream(srcFile); //创建文件输入流
       byte[] bytes =new byte[fis.available()];//创建一个byte数组大小为fis数据的长度
           fis.read(bytes);//将数据读取到bytes数组
       fis.close();//关闭输入输出流
       List<Node> list =getNode(new String(bytes)); //得到bytes数组对应的Node集合

        Node root =CreateHuffmanTree(list); //得到哈夫曼树的根结点
       getCode(root,"",stringBuilder);//得到huffman编码表 存入到了 开头的static HashMap<Character,String> huffmanTreeMapCode =new HashMap<>();
       byte[] b=zip(huffmanTreeMapCode,new String(bytes)); // 将byte数组经过哈夫曼编码后的编码再次转为byte数组
        System.out.println("huffman编码转为byte");
        for(byte bb:b)
        {
            System.out.print(bb+" ");
        }
        System.out.println();
        FileOutputStream fos = new FileOutputStream(dstFile);//创建文件输出流

        fos.write(b);//将其写入到压缩文件中
        fos.close();//关闭输入输出流
    }
  //第七步 对压缩成的byte数组进行解码
    public static String Decode(byte[] bytes,Map<Character,String> huffmanTreeMapCode )//解码  得到的是一个字符串
    {
             StringBuilder stringBuilder =new StringBuilder();
           /*  last =Integer.toBinaryString(bytes[bytes.length-1]).length(); *///判断最后一个byte数组的二进制位数是多少
             for(int i=0;i<bytes.length;i++) {   //遍历byte数组 将其转化为二进制 拼接成字符串
              if(i!=bytes.length-1)   stringBuilder.append(byteToBinary(bytes[i], true));
               else stringBuilder.append(byteToBinary(bytes[i], false));
             }
       System.out.println("解码byte转为二进制:");
        System.out.println(stringBuilder);

         Map<String,Character> DecodeMap=new HashMap<String,Character>();  //创建一个字典
             for(char ch:huffmanTreeMapCode.keySet())//对huffman编码表进行遍历
             DecodeMap.put(huffmanTreeMapCode.get(ch),ch);//将huffman编码表的key作为键值存入新的字典 //键值作为key存入新的字典
             StringBuilder s =new StringBuilder();
             for(int i=0;i<stringBuilder.length();i++) //对二进制拼接而成的字符串进行解码 //用新的字典
             {   boolean flag =true;  //标记 默认为true
                 int count=1;//计步器
                String b= "null";//设为空

                 while(flag)
                 {
                     String key = stringBuilder.substring(i, i + count);  //从字符串第0个开始取 取到第i+count个,作为key
                     b = String.valueOf(DecodeMap.get(key));//如果在字典中找到key 则返回的不是null ,没找到就返回null

                     if(b=="null")//如果没找到
                     {
                         count++; //就让count++
                     }else {
                         //如果找到 就结束循环
                         flag=false;
                     }


                 }
                 s.append(b);//然后将其添加到字符串拼接类中
                 i=i+count-1;//然后i跳转到i+count-1  为什么减1 因为i++需要抵消掉

             }
        System.out.println(s.toString());
             return s.toString(); //返回解码之后的字符串


    }
    //第六步 byte转二进制的工具类
    public static String byteToBinary(byte b,boolean flag)//byte转二进制
    {
       int temp=b; //将byte转为int
       temp|=256;//将temp或上256  256的二进制码为1 0000 0000
       String str =Integer.toBinaryString(temp);//int转二进制
     if(flag)  return str.substring(str.length()-8);  //如果他不是最后一个数 就正常8位取
      else  return str.substring(str.length()-last); //如果他是最后一个数 就先判断最后一个数的二进制位数是多少 然后再添加
    }
    //第五步 根据哈夫曼编码表进行编码
    public static byte[] zip(Map<Character,String> huffmanTreeMapCode,String s)//压缩 得到的是一个byte数组
    {   char []arr=s.toCharArray();//字符串转化为char数组
        StringBuilder stringBuilder =new StringBuilder();  //方便字符串拼接
        for(char ch:arr)//遍历char数组
        {
            stringBuilder.append(huffmanTreeMapCode.get(ch));//哈夫曼编码的key就是ch 键值就是哈夫曼编码表的编码
        }
        System.out.println("哈夫曼编码过后:");
        System.out.println(stringBuilder.toString());//此时得到的是哈夫曼编码 但其长度很长  将其转为byte
        int len =stringBuilder.length()%8==0?stringBuilder.length()/8:stringBuilder.length()/8+1; //byte 每8位一个 所以数组长度就是除8 如果有剩余就加1
        byte [] bytes =new byte[len];//压缩的byte数组
        last=stringBuilder.length()%8;
        String str = stringBuilder.toString();//将字符串拼接类转为字符串
        int index=0;//计步器
        for(int i=0;i<stringBuilder.length();i+=8) //i每次+8代表每次字符串每次截取8位
        {
             if((i+8)<stringBuilder.length())//如果i+8小于字符串的长度
                 bytes[index++]=(byte)Integer.parseInt(str.substring(i,i+8),2);//就讲字符串以二进制的方式转化为int 再强转为byte
            else//如果大于字符串的长度
                bytes[index++]=(byte)Integer.parseInt(str.substring(i),2);//就只要i及i之后的字符串以二进制的方式转化为int 再强转为byte

        }
        return bytes; //返回byte数组
    }
    //第四步得到哈夫曼编码表    路径值 左路径为0 右路径为1
    public static void  getCode(Node root ,String code ,StringBuilder stringBuilder)//传入根结点,一个路径值,一个StringBuilder
    {
        StringBuilder stringBuilder1 =new StringBuilder(stringBuilder);//创建上一个已存入路径值的StringBuilder 这是为了拼接路径值
        stringBuilder1.append(code);//添加路径值
        if(root!=null)//如果根节点不为空
        {
            if(root.english=='\0')//节点为空时代表他不是叶子节点,每个字母都保存在叶子节点之中
            {
                getCode(root.left,"0",stringBuilder1);//先遍历左路径
                getCode(root.right,"1",stringBuilder1);//再遍历右路径
            }else//当结点为叶子结点时
                {
                huffmanTreeMapCode.put(root.english,stringBuilder1.toString());//将其存入一个字典中 其key为char,键值为当前叶子结点的字符串拼接的路径值
            }
        }
    }
    //第二步得到存有node结点的集合
    public static List<Node> getNode(String s)//计算一句话中的每个字母出现次数并将赋值到node里并装入到集合中
    {
        char[] arr =  s.toCharArray(); //将字符串转化为字符数组
        ArrayList<Node> arrayList=new ArrayList<Node>();//创建一个存 Node类型的集合
        Map<Character,Integer> nodesMap =new HashMap<>();//创建一个 key 为char,键值为int的字典
        for(char c:arr)//增强for遍历 字符数组
            if(!nodesMap.containsKey(c))//判断这个字典的key值是否有字符c
            {   //如果没有 就加入这个字符 并且指定键值为1
                nodesMap.put(c,1);
            }else nodesMap.put(c, nodesMap.get(c)+1);//如果有 则让键值加1
            for(char ch :nodesMap.keySet())//遍历字典的key
                arrayList.add(new Node(nodesMap.get(ch),ch));//加入结点 结点的权值就是 map的键值,字符就是字典的key值
        showListNode(arrayList);
        return arrayList;//返回装着结点的集合
    }
    //第三步得到创建哈夫曼树的根节点
    public static Node CreateHuffmanTree(List<Node> list){//创建哈夫曼树  返回树的根结点
          Collections.sort(list);//先按权值从小到大排序
        while(list.size()>1)// 一直循环到集合中只剩一个结点
        {
            Node leftNode =list.get(0);//左孩子为第一个结点
            Node rightNode=list.get(1);//右孩子为第二个结点
            Node parentNode=new Node(leftNode.weight+rightNode.weight,'\0');//父亲结点的权值为左孩子和右孩子的权值之和,字符为空
            parentNode.left=leftNode;//父母的左孩子为leftnode
            parentNode.right=rightNode;//父母的右孩子为rightnode
            list.remove(leftNode);//删除第一小的结点
            list.remove(rightNode);//删除第二小的结点
            list.add(parentNode);//加入父亲结点
            Collections.sort(list);//再次排序
        }
        if(list.size()==0)
        {
           try {

               throw new RuntimeException();
           }catch (Exception e)
           {
               System.out.println("根节点为空,请在被压缩文件中添加内容");
           }
        }

        return list.get(0);//集合中只有一个结点,返回的第一个结点就是根结点


    }
    public static void preOrder(Node root)//树的前序遍历
    {
        System.out.println(root);
        if(root.left!=null)
            preOrder(root.left);
        if(root.right!=null)
            preOrder(root.right);
    }
    public static void showListNode(List<Node> list)
    {
        for(Node node:list)
        {
            System.out.println(node);
        }
    }
    }
//第一步创建结点类
class Node implements Comparable<Node> {
    int weight;//权值
    char english;//字符
    Node left;//左孩子
    Node right;//右孩子
    public Node(int weight, char english)
    {
        this.weight=weight;
        this.english=english;
    }


    @Override
    public String toString() {
        return "Node{" +
                " 字符=" + english +
                ",权重=" + weight +
                '}' ;
    }

    @Override
    public int compareTo(Node o) {
        return this.weight-o.weight;
    }//自定义排序
}

原文件数据长度8001 e9620cfb08e14c9ab8841ce3f5fad246.png

压缩后文件数据长度3396 fc367cd116f64f5a8c3e49a52a426e0f.png

还原文件数据长度8001 5b9b5d35dea04eea8efdfdb5598483fd.png

 

 

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

humannoid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值