基于哈夫曼编码的单文件压缩/解压项目

基于哈夫曼编码的单文件压缩/解压项目


项目语言:Java

项目地址: https://github.com/Mazai-Liu/Compress


哈夫曼编码

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

通过构造哈弗曼树(也称最优二叉树),可以得到哈弗曼编码。

哈夫曼编码实现可以应用于数据压缩,主要有以下两点原因:

  1. 哈夫曼编码是前缀编码,不会有二义性
  2. 哈夫曼树即最优二叉树,是带权路径长度最短的树,故字符编码的总长最短

项目实现思路及流程(完整代码见github)

源文件每个字节存储所需8位bit,通过构造哈夫曼树得到带权路径最小的字节编码。此编码长度不一定是8位bit,且出现频率越高的字节,编码长度越短。

把每个字节按编码表写入压缩后的文件,解压时再根据相同原理解码。(解码不会有二义性)

1 构造字节出现的频率表

    public static int[] frequency = new int[256];

    ...

    // OFFSET作为偏移量,用以处理中文
    // frequency为频率表
    BufferedInputStream in = null;
    try {
        in = new BufferedInputStream(new FileInputStream(filePath));
        byte[] buf = new byte[1024 * 10];
        int left;
        while((left = in.read(buf)) > 0){
            // 统计字符出现频率
            for(int i = 0;i < left;i++)
                frequency[buf[i] + OFFSET]++;
        }

2 根据频率表,构造哈弗曼树

    public class TNode implements Comparable<TNode>{
        public byte value;
        public int fre;

        public TNode left;
        public TNode right;

        public boolean isLeaf;

        public TNode(byte value, int fre){
            ...
        }

        public TNode(int fre){
            ...
        }

        public int compareTo(TNode tNode) {
            return this.fre - tNode.fre;
        }
    }

    public void makeHuffman(){
        // make original queue
        Queue<TNode> queue = new PriorityQueue();
        for(int i = 0;i < 256;i++){
            if(frequency[i] > 0){
                queue.add(new TNode((byte)(i - OFFSET), frequency[i]));
            }
        }

        while(queue.size() > 1){
            TNode left = queue.poll();
            TNode right = queue.poll();

            TNode newNode = new TNode(left.fre + right.fre);
            newNode.left = left;
            newNode.right = right;

            queue.add(newNode);
        }

        //root 为哈弗曼树的根节点
        root = queue.poll();
    }

3 根据哈弗曼树,构造编码表(字节 --> 编码)

    public void makeCodeTable(){
        dfs(root, "");
    }

    void dfs(TNode root, String code){
        if(root.isLeaf){
            encodeTable.put(root.value, code);
            return;
        }
        dfs(root.left, code + "0");
        dfs(root.right, code + "1");
    }

4 根据编码表,把源文件按字节编码,按一定格式生成压缩文件

压缩文件的格式为:魔数 + 源文件大小 + 频率表大小 + 频率表 + 压缩后数据大小(bit) + 压缩后的数据

//   magic total lengthOfFreTable freTable lengthOfCompressedData CompressedData
//   1B     4B        4B             xB           4B                   xB
    public void doCompression(String filePath){
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream(file));
            ...
            out.write(MAGIC);   // 魔数
            writeInt(out, ORIGIN_TOTAL);    // 源文件大小
            writeInt(out, 256);     // 频率表大小
            writeFre(out);      // 频率表

            // 组合01串,再把01串按位写入压缩文件中
            writeCompressedData(out, in);   // 写入压缩后数据
            ...
            out.flush();
        }catch(){}
    }

    /**
     * 不能直接 write(int x)。虽然x是一个4字节的int,但只会只写入x的最后一字节。
     * 所以需要封装一个方法,真正写入4字节。通过位运算不断取v的8位填充到字节数组中。
     */
    public static void writeInt(BufferedOutputStream out, int v) throws IOException {
        byte[] buf = new byte[4];
        buf[0] = (byte) ((v >> 24) & 0xFF);
        buf[1] = (byte) ((v >> 16) & 0xFF);
        buf[2] = (byte) ((v >> 8) & 0xFF);
        buf[3] = (byte) (v & 0xFF);
        out.write(buf);
    }

5 按相同原理解码

public void readHead(String filePath){
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(filePath));
            // 魔数
            byte[] magic = new byte[1];
            in.read(magic);
            if(magic[0] != 'k'){
                System.out.println("file format error!");
                System.exit(-1);
            }

            // 总大小
            checkTotalByte = readInt(in);

            // 频率表长
            tableLength = readInt(in);

            // 读取频率表
            readFre(in);

            // 构建哈弗曼树
            makeHuffman();

            // 根据哈弗曼树解压
            process(in);
        } catch () {}
    }
    // 完整代码见github
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值