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开发实现图片URL地址检验,如何编码?
【10月更文挑战第14天】Java开发实现图片URL地址检验,如何编码?
64 4
|
28天前
|
Java
Java实现随机生成某个省某个市的身份证号?如何编码?
【10月更文挑战第18天】Java实现随机生成某个省某个市的身份证号?如何编码?
101 5
|
1月前
|
Java
Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
【10月更文挑战第14天】Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
56 2
|
3月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
112 0
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
67 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
1月前
|
安全 Java Android开发
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
50 5
|
2月前
|
安全 Java Android开发
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
JavaWeb解压缩漏洞之ZipSlip与Zip炸弹
101 2
|
2月前
|
存储 移动开发 Java
java核心之字符串与编码
java核心之字符串与编码
22 2
|
2月前
|
算法 Java
Java 压缩文件
在Java中压缩文件是一个常见的需求,通常可以通过使用Java自带的`java.util.zip`包来实现。这个包提供了`ZipOutputStream`类来创建ZIP格式的压缩文件。以下是一个简单的示例,展示了如何将多个文件压缩到一个ZIP文件中。 ### 示例:将多个文件压缩到一个ZIP文件中 ```java import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class ZipFilesExample { public static vo
|
3月前
|
Java
Java系列之:字符串UTF-8 编码格式转换位 UTF-32 【生僻字截取问题】
这篇文章讨论了在Java中处理包含生僻字的字符串时可能遇到的问题,并提供了一种解决方法:将字符串的编码格式从UTF-8转换为UTF-32,以确保每个字符都占用固定的字节数,从而避免在截取操作中破坏字符,示例代码展示了如何进行编码转换和字符串截取。