Java哈夫曼编码实现压缩与解压

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

java哈夫曼编码实现文件压缩与解压

压缩

第一步: 创建输入流

第二步: 将输入流中的数据 存入到bytesFile中

第三步: 哈夫曼树操作

1.将每一个字节装入到一个Node类中
2.根据nodes 构建哈夫曼树
3.根据哈夫曼树 生成对应的哈夫曼编码Map集合
4.将生成的哈夫曼编码,压缩得到压缩后的赫夫曼编码字节数组

第四步: 创建输出流 , 将压缩后的 文件进行输出

第五步: 因为FileOutputStream中 没有把Map集合写入到文件的方法,
所以需要用ObjectOutputStream 将Map集合 封装成对象,写入到文件。

第六步: 把赫夫曼编码后的 字节数组 压入压缩文件中

第七步: 把哈夫曼编码 压入压缩文件中 (如果不压哈夫曼编码表 后期就没有办法解压文件)



解压

第一步: 创建输入流

第二步: 将获取的数据进行解压操作

第三步: 哈夫曼树解压操作

1.获取每个字符出现的次数,即权值
2.利用之前后的的权值,还原哈夫曼树
3.找到对应的叶子节点,将信息保存到解压文件中

第四步: 创建输出流写出文件


更多细节,请看代码中的注释

//利用哈夫曼编码实现 解压与压缩
public class HuffmanTreeCode {
    public static void main(String[] args) {

        //对文件进行压缩操作
        String srcFile="d://codeTemp.jpg";
        String zipFile="d://codeTemp.zip";
        zipFile(srcFile, zipFile);
        System.out.println("文件压缩成功~");

        //对文件进行解压操作
//        String zipFile = "d://codeTemp.zip";
//        String unZipFile = "d://codeTemp1.jpg";
//        unZipFile(zipFile, unZipFile);
//        System.out.println("文件解压成功。。。");


    }

    /**
     * 对文件进行解压操作
     *
     * @param zipFile 带解压文件的全路径
     * @param dstFile 解压后文件放到哪里(目标路径)
     */
    public static void unZipFile(String zipFile, String dstFile) {
        FileInputStream is = null;
        ObjectInputStream ois = null;
        FileOutputStream os = null;

        try {
            //创建输入流
            is = new FileInputStream(zipFile);
            //将获取的数据进行解压操作
            ois = new ObjectInputStream(is);

            byte[] huffmanBytes = (byte[]) ois.readObject();
            Map<Byte, String> huffmanCodes = (Map<Byte, String>) ois.readObject();
            byte[] srcbytes = huffmanUnZip(huffmanCodes, huffmanBytes);

            //创建输出流
            os = new FileOutputStream(dstFile);
            os.write(srcbytes);

        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                os.close();
                ois.close();
                is.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }

    }


    /**
     * 对文件进行压缩操作
     *
     * @param srcFile 文件的原始路径
     * @param dstFile 文件的目标路径
     */
    public static void zipFile(String srcFile, String dstFile) {
        FileInputStream is = null;
        OutputStream os = null;
        ObjectOutputStream oos = null;

        try {
            //创建输入流
            is = new FileInputStream(srcFile);
            //将输入流中的数据 存入到bytesFile中
            byte[] bytesFile = new byte[is.available()];
            is.read(bytesFile);
            byte[] bytes = huffmanZip(bytesFile);

            //创建输出流 , 将压缩后的 文件进行输出
            os = new FileOutputStream(dstFile);
            //因为FileOutputStream中 没有把Map集合写入到文件的方法,
            // 所以需要用ObjectOutputStream 将Map集合 封装成对象,写入到文件。
            oos = new ObjectOutputStream(os);
            //把赫夫曼编码后的 字节数组 压入压缩文件中
            oos.writeObject(bytes);
            //把哈夫曼编码  压入压缩文件中 (如果不压哈夫曼编码表  后期就没有办法解压文件)
            oos.writeObject(huffmanMap);

        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                oos.close();
                os.close();
                is.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }


    }


    /**
     * 将传进来的二进制数组进行转化
     *
     * @param haffmanMap
     * @param srcbytes   待转化的原始二进制数组
     * @return 压缩前的样子
     */
    private static byte[] huffmanUnZip(Map<Byte, String> haffmanMap, byte[] srcbytes) {

        String s = bytesToBitsting(srcbytes);

        Map<String, Byte> map = new HashMap<>();
        for (Map.Entry<Byte, String> maps : haffmanMap.entrySet()) {
            map.put(maps.getValue(), maps.getKey());
        }
        int count = 1;
        boolean flage = true;
        Byte b = null;
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < s.length(); ) {
            String temp = null;
            while (flage) {
                temp = s.substring(i, i + count);
                b = map.get(temp);
                if (b != null) {
                    i += count;
                    count = 1;
                    flage = false;
                } else {
                    count++;
                }
            }
            list.add(b);
            flage = true;
        }
        byte[] bytes = new byte[list.size()];
        for (int i = 0; i < list.size(); i++) {
            bytes[i] = list.get(i);
        }
        return bytes;
    }


    /**
     * 将转化后的数组 转化为二进制数字符串
     *
     * @param bytes 压缩后的数组
     * @return 该bytes数组对应的二进制字符串
     */
    private static String bytesToBitsting(byte[] bytes) {
        boolean flag = true;
        String str = "";
        for (int i = 0; i < bytes.length; i++) {
            int b = bytes[i];
            if (i != bytes.length - 1) {
                b=b|256;
            } else {
                flag = false;
            }
            String s = Integer.toBinaryString(b);
            if (flag) {
                String substring = s.substring(s.length() - 8);
                str += substring;
            } else {
                str += s;
            }
        }
        return str;
    }


    /**
     * 封装哈夫曼编码的每一步
     *
     * @param contentByte 原始的字符串对应的字节数组
     * @return 返回经过哈夫曼编码后的数组
     */
    private static byte[] huffmanZip(byte[] contentByte) {
        //第一步:将每一个字节装入到一个Node类中
        List<Node> nodes = getNodes(contentByte);
        //第二步:根据nodes  构建哈夫曼树
        Node nodeRoot = creatHuffmanTree(nodes);
        //第三步:根据哈夫曼树  生成对应的哈夫曼编码Map集合
        creatHuffmanCode(nodeRoot);
        //第四步:将生成的哈夫曼编码,压缩得到压缩后的赫夫曼编码字节数组
        byte[] zip = zip(contentByte, huffmanMap);
        return zip;
    }


    private static byte[] zip(byte[] contentByte, Map<Byte, String> huffmanMap) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte bytes : contentByte) {
            stringBuilder.append(huffmanMap.get(bytes));
        }

        //等价于 len=(stringBuilder.length()+7)/8
        int len;
        //len表示 有多少个byte,每个byte中含有8个位
        if (stringBuilder.length() % 8 == 0) {
            len = stringBuilder.length() / 8;
        } else {
            len = stringBuilder.length() / 8 + 1;
        }
        byte[] huffmanByte = new byte[len];
        int index = 0;
        for (int i = 0; i < stringBuilder.length(); i += 8) {

            if (i + 8 > stringBuilder.length()) {
                huffmanByte[index] = (byte) Integer.parseInt(stringBuilder.substring(i), 2);
            } else {
                huffmanByte[index] = (byte) Integer.parseInt(stringBuilder.substring(i, i + 8), 2);
            }
            index++;
        }

        return huffmanByte;
    }


    //定义一个Map集合去存储每个子结点的信息
    static Map<Byte, String> huffmanMap = new HashMap<>();
    //定义一个StringBuilder去存储某一叶子结点的路径
    static StringBuilder stringBuilder = new StringBuilder();

    private static void creatHuffmanCode(Node node) {
        creatHuffmanCode(node, "", stringBuilder);
    }

    /**
     * 将每一个叶子结点对应的二进制编码输出出来
     *
     * @param node          根结点
     * @param track         边,左子树为0   右子树为1
     * @param stringBuilder 一个叶子节点的路径整个路径
     */
    private static void creatHuffmanCode(Node node, String track, StringBuilder stringBuilder) {
        //构造一个初始化为指定字符串内容的字符串构建器。
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        stringBuilder2.append(track);
        if (node != null) {
            //如果data为空,则不是叶子结点
            if (node.data == null) {
                creatHuffmanCode(node.leftNode, "0", stringBuilder2);
                creatHuffmanCode(node.rightNode, "1", stringBuilder2);
            } else {
                //如果找到叶子节点就将该结点和该系结点的值  加入到Map集合中
                huffmanMap.put(node.data, stringBuilder2.toString());
            }
        }

    }

    private static List<Node> getNodes(byte[] contentByte) {
        //用于存放 最后的哈夫曼树
        List nodes = new ArrayList<Node>();

        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : contentByte) {
            Integer count = counts.get(b);
            if (count == null) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }

        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    //构造哈夫曼树
    public static Node creatHuffmanTree(List<Node> nodes) {
        if (nodes == null) {
            return null;
        }

        while (nodes.size() > 1) {
            //将nodes按照  从小到打的顺序排列
            Collections.sort(nodes);
            Node left = nodes.get(0);
            Node right = nodes.get(1);
            Node parent = new Node(null, left.weight + right.weight);
            parent.leftNode = left;
            parent.rightNode = right;
            nodes.remove(left);
            nodes.remove(right);
            nodes.add(parent);
        }
        return nodes.get(0);
    }

    public static void preOrder(Node root) {
        if (root == null) {
            System.out.println("为空,前序遍历失败");
        } else {
            root.preOrder();
        }
    }

}


class Node implements Comparable<Node> {
    Byte data;
    int weight;
    Node leftNode;
    Node rightNode;

    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }


    @Override
    public int compareTo(Node o) {
        return this.weight - o.weight;
    }

    public void preOrder() {
        if (this == null) {
            return;
        }
        System.out.println(this);
        if (this.leftNode != null) {
            this.leftNode.preOrder();
        }
        if (this.rightNode != null) {
            this.rightNode.preOrder();
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
}
目录
相关文章
|
1月前
|
存储 Java 文件存储
如何用 Java 压缩 ZIP 文件?
【2月更文挑战第21天】
34 1
|
3月前
|
存储 Java Android开发
IO流:java中解码和编码出现乱码说明及代码实现
IO流:java中解码和编码出现乱码说明及代码实现
|
4月前
|
Java 关系型数据库 Linux
Linux|Java|jar包的解压和重新打包(更新配置)
Linux|Java|jar包的解压和重新打包(更新配置)
72 0
|
3月前
|
存储 算法 Java
从零开始学习 Java:简单易懂的入门指南之IO序列化、打印流、压缩流(三十三)
从零开始学习 Java:简单易懂的入门指南之IO序列化、打印流、压缩流(三十三)
|
5月前
|
Java
java 读取文件 获取byte[]字节 并执行Gzip的压缩和解压
java 读取文件 获取byte[]字节 并执行Gzip的压缩和解压
49 0
|
5月前
|
Java
Java实现多文件打包成压缩包下载
Java实现多文件打包成压缩包下载
165 0
|
13天前
|
Java API
编码的奇迹:Java 21引入有序集合,数据结构再进化
编码的奇迹:Java 21引入有序集合,数据结构再进化
16 0
|
13天前
|
Java Shell
Java 21颠覆传统:未命名类与实例Main方法的编码变革
Java 21颠覆传统:未命名类与实例Main方法的编码变革
13 0
|
2月前
|
Java Maven
java获取文件编码,jsoup获取html纯文本
java获取文件编码,jsoup获取html纯文本
13 0
|
4月前
|
Java API Apache
Java之打印流,压缩流,工具包的详细解析
4. 打印流 4.1 概述 平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。 4.2 PrintStream类
32 0