踩过无数坑实现的哈夫曼压缩(JAVA)

最近可能又是闲着没事干了,就想做点东西,想着还没用JAVA弄过数据结构,之前搞过算法,就试着写写哈夫曼压缩了。

本以为半天就能写出来,结果,踩了无数坑,花了整整两天时间!!orz。。。不过这次踩坑,算是又了解了不少东西,更觉得在开发中学习是最快的了。

话不多说,进入正题

首先先来讲讲哈夫曼树

哈夫曼树属于二叉树,即树的结点最多拥有2个孩子结点。若该二叉树带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树的构造

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

哈夫曼编码

在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。例如,需传送的报文为“HELLO WORLD”,这里用到的字符集为“D,E,H,L,O,R,W”,各字母出现的次数为{1,1,1,3,2,1,1}。现要求为这些字母设计编码。要区别7个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用000、001、010、011、100、101、110对“D,E,H,L,O,R,W”进行编码发送,当对方接收报文时再按照三位一分进行译码。显然编码的长度取决报文中不同字符的个数。若报文中可能出现26个不同字符,则固定编码长度为5。然而,传送报文时总是希望总长度尽可能短。在实际应用中,各个字符的出现频度或使用次数是不相同的,如A、B、C的使用频率远远高于X、Y、Z,自然会想到设计编码时,让使用频率高的用短编码,使用频率低的用长编码,以优化整个报文编码。

此时D->0000 E->0001 W->001 H->110 R->111 L->01 0->02

固定三位时编码长度为30,而时候哈夫曼编码后,编码长度为27,很明显长度缩小了,得到优化。

下面就是代码实现

HuffmanCompress.java

package 哈夫曼;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.PriorityQueue;

public class HuffmanCompress {
    private PriorityQueue<HufTree> queue = null;

    public void compress(File inputFile, File outputFile) {
        Compare cmp = new Compare();
        queue = new PriorityQueue<HufTree>(12, cmp);

        // 映射字节及其对应的哈夫曼编码
        HashMap<Byte, String> map = new HashMap<Byte, String>();

        int i, char_kinds = 0;
        int char_tmp, file_len = 0;
        FileInputStream fis = null;
        FileOutputStream fos = null;
        DataOutputStream oos = null;

        HufTree root = new HufTree();
        String code_buf = null;

        // 临时储存字符频度的数组
        TmpNode[] tmp_nodes = new TmpNode[256];

        for (i = 0; i < 256; i++) {
            tmp_nodes[i] = new TmpNode();
            tmp_nodes[i].weight = 0;
            tmp_nodes[i].Byte = (byte) i;
        }

        try {
            fis = new FileInputStream(inputFile);
            fos = new FileOutputStream(outputFile);
            oos = new DataOutputStream(fos);

            /*
             * 统计字符频度,计算文件长度
             */
            while ((char_tmp = fis.read()) != -1) {
                tmp_nodes[char_tmp].weight++;
                file_len++;
            }
            fis.close();
            // 排序,将频度为0的字节放在最后,同时计算除字节的种类,即有多少个不同的字节
            Arrays.sort(tmp_nodes);
            for (i = 0; i < 256; i++) {
                if (tmp_nodes[i].weight == 0) {
                    break;
                }
                HufTree tmp = new HufTree();
                tmp.Byte = tmp_nodes[i].Byte;
                tmp.weight = tmp_nodes[i].weight;
                queue.add(tmp);
            }
            char_kinds = i;

            if (char_kinds == 1) {
                oos.writeInt(char_kinds);
                oos.writeByte(tmp_nodes[0].Byte);
                oos.writeInt(tmp_nodes[0].weight);
            } else {
                // 建树
                createTree(queue);
                root = queue.peek();
                // 生成哈夫曼编码
                hufCode(root, "", map);
                // 写入字节种类
                oos.writeInt(char_kinds);
                for (i = 0; i < char_kinds; i++) {
                    oos.writeByte(tmp_nodes[i].Byte);
                    oos.writeInt(tmp_nodes[i].weight);
                }
                oos.writeInt(file_len);
                fis = new FileInputStream(inputFile);
                code_buf = "";
                while ((char_tmp = fis.read()) != -1) {
                    code_buf += map.get((byte) char_tmp);
                    while (code_buf.length() >= 8) {
                        char_tmp = 0;
                        for (i = 0; i < 8; i++) {
                            char_tmp <<= 1;
                            if (code_buf.charAt(i) == ‘1‘)
                                char_tmp |= 1;
                        }
                        oos.writeByte((byte) char_tmp);
                        code_buf = code_buf.substring(8);
                    }
                }
                // 最后编码长度不够8位的时候,用0补齐
                if (code_buf.length() > 0) {
                    char_tmp = 0;
                    for (i = 0; i < code_buf.length(); ++i) {
                        char_tmp <<= 1;
                        if (code_buf.charAt(i) == ‘1‘)
                            char_tmp |= 1;
                    }
                    char_tmp <<= (8 - code_buf.length());
                    oos.writeByte((byte) char_tmp);
                }
                oos.close();
                fis.close();
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void extract(File inputFile, File outputFile) {
        Compare cmp = new Compare();
        queue = new PriorityQueue<HufTree>(12, cmp);

        int i;
        int file_len = 0;
        int writen_len = 0;
        FileInputStream fis = null;
        FileOutputStream fos = null;
        DataInputStream ois = null;

        int char_kinds = 0;
        HufTree root=new HufTree();
        byte code_tmp;
        try {
            fis = new FileInputStream(inputFile);
            ois = new DataInputStream(fis);
            fos = new FileOutputStream(outputFile);

            char_kinds = ois.readInt();
            // 字节只有一种
            if (char_kinds == 1) {
                code_tmp = ois.readByte();
                file_len = ois.readInt();
                while ((file_len--) != 0) {
                    fos.write(code_tmp);
                }
            } else {
                for (i = 0; i < char_kinds; i++) {
                    HufTree tmp = new HufTree();
                    tmp.Byte = ois.readByte();
                    tmp.weight = ois.readInt();
                    System.out.println("Byte: "+tmp.Byte+" weight: "+tmp.weight);
                    queue.add(tmp);
                }

                createTree(queue);

                file_len = ois.readInt();
                root = queue.peek();
                while (true) {
                    code_tmp = ois.readByte();
                    for (i = 0; i < 8; i++) {              //这里为什么是&128呢?              //我们是按编码顺序走的,1向右,0向左,对于一串byte编码有8位,那最高位就是2^7,就是128              //所以通过位运算来判断该位是0还是1              //之前我想错了,从后面开始走,结果乱码,压缩在这块也卡了好久orz
                        if ((code_tmp&128)==128) {
                            root = root.rchild;
                        } else {
                            root = root.lchild;
                        }
                        if (root.lchild == null && root.rchild == null) {
                            fos.write(root.Byte);
                            ++writen_len;
                            if (writen_len == file_len)
                                break;
                            root = queue.peek();
                        }
                        code_tmp <<= 1;
                    }
                    if (writen_len == file_len)
                        break;
                }
            }
            fis.close();
            fos.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void createTree(PriorityQueue<HufTree> queue) {
        while (queue.size() > 1) {
            HufTree min1 = queue.poll();
            HufTree min2 = queue.poll();
            System.out.print(min1.weight + " " + min2.weight + " ");

            HufTree NodeParent = new HufTree();
            NodeParent.weight = min1.weight + min2.weight;
            NodeParent.lchild = min1;
            NodeParent.rchild = min2;

            queue.add(NodeParent);
        }
    }

    public void hufCode(HufTree root, String s, HashMap<Byte, String> map) {
        if (root.lchild == null && root.rchild == null) {
            root.code = s;
            System.out.println("节点" + root.Byte + "编码" + s);
            map.put(root.Byte, root.code);

            return;
        }
        if (root.lchild != null) {
            hufCode(root.lchild, s + ‘0‘, map);
        }
        if (root.rchild != null) {
            hufCode(root.rchild, s + ‘1‘, map);
        }

    }

}

Compare.java

package 哈夫曼;

import java.util.Comparator;

public class Compare implements Comparator<HufTree>{

    @Override
    public int compare(HufTree o1, HufTree o2) {
        if(o1.weight < o2.weight)
            return -1;
        else if(o1.weight > o2.weight)
            return 1;
        return 0;
    }

}

这里涉及到JAVA中优先对列的重载排序,我之前一直按照C++中的重载来写,结果发现发现压缩后的大小是原文件的3倍!!!!然后还一直以为是压缩过程的问题,疯狂看压缩过程哪里错了,最后输出了下各字符的编码才发现问题,耗了我整整一天TAT。。附上一个对优先队列重载讲解的链接https://blog.csdn.net/u013066244/article/details/78997869

HufTree.java

package 哈夫曼;

public class HufTree{
    public byte Byte; //以8位为单元的字节
    public int weight;//该字节在文件中出现的次数
    public String code; //对应的哈夫曼编码
    public HufTree lchild,rchild;

}

//统计字符频度的临时节点
class TmpNode implements Comparable<TmpNode>{
    public byte Byte;
    public int weight;

    @Override
    public int compareTo(TmpNode arg0) {
        if(this.weight < arg0.weight)
            return 1;
        else if(this.weight > arg0.weight)
            return -1;
        return 0;
    }
}

test.java

package 哈夫曼;

import java.io.File;

public class test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        HuffmanCompress sample = new HuffmanCompress();
    //    File inputFile = new File("C:\\Users\\long452a\\Desktop\\opencv链接文档.txt");
    //   File outputFile = new File("C:\\Users\\long452a\\Desktop\\opencv链接文档.rar");
    //    sample.compress(inputFile, outputFile);
        File inputFile = new File("C:\\Users\\long452a\\Desktop\\opencv链接文档.rar");
            File outputFile = new File("C:\\Users\\long452a\\Desktop\\opencv链接文档1.txt");
           sample.extract(inputFile, outputFile);
    }

}

原文地址:https://www.cnblogs.com/csu-lmw/p/9655573.html

时间: 2024-10-14 03:32:30

踩过无数坑实现的哈夫曼压缩(JAVA)的相关文章

赫夫曼树JAVA实现及分析

一,介绍 1)构造赫夫曼树的算法是一个贪心算法,贪心的地方在于:总是选取当前频率(权值)最低的两个结点来进行合并,构造新结点. 2)使用最小堆来选取频率最小的节点,有助于提高算法效率,因为要选频率最低的,要么用排序,要么用堆.用堆的话,出堆的复杂度为O(logN),而向堆中插入一个元素的平均时间复杂度为O(1),在构建赫夫曼树的过程中,新生成的结点需要插入到原来的队列中,故用堆来维持这种顺序比排序算法要高效地多. 二,赫夫曼算法分析 ①用到的数据结构分析 首先需要构造一棵赫夫曼树,因此需要二叉链

霍夫曼树 java实现

作为一个通信人,本科时候上过信息论,研究生也继续修过信息编码.面试的时候,面试官说了一个霍夫曼树,作为一个通信人竟然忘了.多少有些说不过去. 理论知识 Huffman算法的最根本的原则是:累计的(字符的统计数字字符的编码长度)为最小,也就是权值(字符的统计数字字符的编码长度)的和最小. 这样编码可以达到压缩的效果.又名最优二叉树. 具体的可以参考左耳朵耗子的博客:http://coolshell.cn/articles/7459.html 很形象. 实现 主要包括:构造树.编码.解码.show

源码编译安装lnmp环境(nginx-1.14.2 + mysql-5.6.43 + php-5.6.30 )------踩了无数坑,重装了十几次服务器才会的,不容易啊!

安装顺序 php --- nginx -- mysql 安装php-5.6.30: 1 环境准备 yum install gcc bison bison-devel zlib-devel libmcrypt-devel mcrypt mhash-devel openssl-devel libxml2-devel libcurl-devel bzip2-devel readline-devel libedit-devel sqlite-devel jemalloc jemalloc-devel y

倒排索引PForDelta压缩算法——基本假设和霍夫曼压缩同

PForDelta算法 PForDelta算法最早由Heman在2005年提出,它允许同时对整个chunk数据(例128个数)进行压缩处理.基础思想是对于一个chunk的数列(例128个),认为其中占多数的x%数据(例90%)占用较小空间,而剩余的少数1-x%(例10%)才是导致数字存储空间过大的异常值.因此,对x%的小数据统一使用较少的b个bit存储,剩下的1-x%数据单独存储. 举个例子,假设我们有一串数列23, 41, 8, 12, 30, 68, 18, 45, 21, 9, ...取b

数据结构:哈夫曼编码(php版)

演示网址:http://huffman.sinaapp.com/ 源文件下载地址:http://xiaocao.u.qiniudn.com/work/huffman-2013-12-19.zip 概述下: 哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩. 在计算机信息处理中,"哈夫曼编码"是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩.     简单的,就是靠权值排序,然后,转码,最优保存. 实现功能: 保存译码:在服务器端保存源

项目实战——基于LZ77变形和哈夫曼编码的GZIP压缩

文件压缩: 日常生活中有很多压缩的例子,比如给很长的名字取一个缩写--西安交通大学简称西交大,这样就给我们的生活提供了很大的便捷,那么什么又是文件压缩呢?文件压缩就是将文件通过一些方法变得更小,解压缩就是将文件还原,文件压缩将文件变得更小节省了内存,并且在网络上传输起来也变得很快,还具有一定的保密性,所以这个项目就是为了实现这个目的. 基于哈夫曼树的文件压缩 一.思想:众所周知在32位平台下一个字节占八个bit位,假如我们文件中的数据是abbbcccccddddddd时,每个字节占用八个比特位,

《C++之那些年踩过的坑(附录一)》

C++之那些年踩过的坑(附录一) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. [版权声明]转载请注明原文来自:http://www.cnblogs.com/GodA/p/6639526.html 本来上个月就开始动笔了,直到现在才发出来,实在太多事情.可能有些小朋友不知道写这一篇随笔的起因,那么你可以看一下我之前写的. 上一篇的最后,我提到了一个问题:代码优化.并留了一个小测试:无符号数与有符号数的性能比

【转载】Fragment 全解析(1):那些年踩过的坑

http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使用姿势3.Fragment之我的解决方案:Fragmentation 本篇主要介绍一些最常见的Fragment的坑以及官方Fragment库的那些自身的BUG,这些BUG在你深度使用时会遇到,比如Fragment嵌套时或者单Activity+多Fragment架构时遇到的坑.如果想看较为实用的技巧,

初学spring boot踩过的坑

一.搭建spring boot环境 maven工程 pom文件内容 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-