20.赫夫曼编码的数据解压

赫夫曼编码的数据解压

上篇是说了用赫夫曼编码进行数据压缩,现在实现对压缩数据的解压。

核心步骤

  • 将原来压缩成byte数组的编码转成一个二进制字符串
  • 对转成的二进制字符串与赫夫曼码表上的数据进行匹配 从而还原原本数据的byte数组
    1. 先将原先码表反转 因为先要通过 value 找key
    2. 对二进制字符串进行遍历 使用指针一直扫描字符串,每扫描到一个字符去码表中匹配,如果匹配到了说明该字符串匹配到了原本的一个byte,加入到一个集合中
核心方法
  /**
     * 将压缩之后的赫夫曼编码重新还原成原字节数组
     *
     * @param huffmanZipCode
     * @return
     */
    public static byte[] unZip(byte[] huffmanZipCode, Map<Byte, String> huffmanCodeTable) {
        // 1. 现将原来压缩成byte数组的编码 还原成二进制字符串
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < huffmanZipCode.length; i++) {
            byte b = huffmanZipCode[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanZipCode.length - 1);
            builder.append(byteToBitString(!flag, b));
        }
        // 2. 比对赫夫曼编码表找到码表上对应原来的 byte
        // 2.1 现将码表的KV反转 因为现在我们是要通过字符串来找byte 反转之后方便操作
        Map<String, Byte> reversHumanCodeMap = new HashMap<>();
        for (Byte key : huffmanCodeTable.keySet()) {
            reversHumanCodeMap.put(huffmanCodeTable.get(key), key);
        }
        // 2.2 因为现在需要将拼接好的二进制编码重新转成原文本byte,需要一根指针
        // 一直扫描字符串,不断的从反转的码表中找,如果找到了匹配说明是一个byte,不然再追加一个字符继续码表中找
        // 创建集合用于保存从码表中匹配到的byte
        List<Byte> originalBytesList = new ArrayList<>();
        int index = 0;
        // 暂存拼接字符串
        String tempStr = "";
        while (index < builder.length()) {
            tempStr += builder.substring(index, index + 1);
            Byte originalByte = reversHumanCodeMap.get(tempStr);
            if (originalByte != null) {
                // 说明码表上找到了一个匹配的byte 重置tempStr
                tempStr = "";
                originalBytesList.add(originalByte);
            }
            index++;
        }
        // list转成byte数组
        byte[] originalContentBytes = new byte[originalBytesList.size()];
        for (int i = 0; i < originalBytesList.size(); i++) {
            originalContentBytes[i] = originalBytesList.get(i);
        }
        return originalContentBytes;
    }

    /**
     * 将一个byte 转成一个二进制的字符串
     *
     * @param b    传入的 byte
     * @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
     * @return 是该b 对应的二进制的字符串,(注意是按补码返回)
     */
    private static String byteToBitString(boolean flag, byte b) {
        //使用变量保存 b
        int temp = b; //将 b 转成 int
        //如果是正数我们还存在补高位
        if (flag) {
            temp |= 256; //按位与 256  1 0000 0000  | 0000 0001 => 1 0000 0001
        }
        String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码
        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }
    }

使用赫夫曼编码压缩和解压文件

压缩
  • 读取文件到一个byte数组中,调用赫夫曼编码压缩方法将数据压缩
  • 将压缩有的byte数组使用OjbectOutputStream输出到一个文件中
  • 继续使用该输出流将赫夫曼编码表也输出到该文件中 便于以后使用码表进行数据的恢复
解压
  • 使用ObjectInputStream读取压缩的字节数组和赫夫曼编码表
  • 使用码表对数据进行解压即可

赫夫曼编码完整代码

public class HuffmanCodeDemo {

    public static void main(String[] args) {
        // 压缩和解压字符串
//        String content = "i like like like java do you like a java";
//        System.out.println(content.getBytes().length);
//        byte[] zipBytes = zip(content.getBytes());
//        byte[] originalContent = unZip(zipBytes, huffmanCodeTable);
//        System.out.println(new String(originalContent));
        // 压缩和解压文件
        zipFile("E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test.txt","E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test-copy.zip");
        unZipFile("E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test-copy.zip","E:\\java_project\\datastruct-algorithm\\src\\main\\resources\\test666.txt");
    }

    /**
     * 赫夫曼码表
     */
    public static Map<Byte, String> huffmanCodeTable = new HashMap<>();

    /**
     * 解压缩文件
     * @param srcFile
     * @param destFile
     */
    public static void unZipFile(String srcFile, String destFile) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        FileOutputStream fos = null;

        try {
            fis = new FileInputStream(srcFile);
            ois = new ObjectInputStream(fis);
            // 读取压缩后的赫夫曼编码
            byte[] zipByteCode = (byte[]) ois.readObject();
            // 读取赫夫曼码表
            Map<Byte, String> huffmanCodeTable = (Map<Byte, String>) ois.readObject();
            byte[] originalContentBytes = unZip(zipByteCode, huffmanCodeTable);
            fos = new FileOutputStream(destFile);
            // 写出
            fos.write(originalContentBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用赫夫曼编码压缩文件
     *
     * @param srcFile
     * @param destFile
     */
    public static void zipFile(String srcFile, String destFile) {
        FileInputStream fis = null;
        ObjectOutputStream oos = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            oos = new ObjectOutputStream(fos);
            byte[] bytes = new byte[fis.available()];
            fis.read(bytes);
            byte[] zipBytes = zip(bytes);
            // 写入赫夫曼压缩之后的字节数组
            oos.writeObject(zipBytes);
            // 将赫夫曼码表也要写入!!不然无法读取码表解码
            oos.writeObject(huffmanCodeTable);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
                fis.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 将压缩之后的赫夫曼编码重新还原成原字节数组
     *
     * @param huffmanZipCode
     * @return
     */
    public static byte[] unZip(byte[] huffmanZipCode, Map<Byte, String> huffmanCodeTable) {
        // 1. 现将原来压缩成byte数组的编码 还原成二进制字符串
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < huffmanZipCode.length; i++) {
            byte b = huffmanZipCode[i];
            //判断是不是最后一个字节
            boolean flag = (i == huffmanZipCode.length - 1);
            builder.append(byteToBitString(!flag, b));
        }
        // 2. 比对赫夫曼编码表找到码表上对应原来的 byte
        // 2.1 现将码表的KV反转 因为现在我们是要通过字符串来找byte 反转之后方便操作
        Map<String, Byte> reversHumanCodeMap = new HashMap<>();
        for (Byte key : huffmanCodeTable.keySet()) {
            reversHumanCodeMap.put(huffmanCodeTable.get(key), key);
        }
        // 2.2 因为现在需要将拼接好的二进制编码重新转成原文本byte,需要一根指针
        // 一直扫描字符串,不断的从反转的码表中找,如果找到了匹配说明是一个byte,不然再追加一个字符继续码表中找
        // 创建集合用于保存从码表中匹配到的byte
        List<Byte> originalBytesList = new ArrayList<>();
        int index = 0;
        // 暂存拼接字符串
        String tempStr = "";
        while (index < builder.length()) {
            tempStr += builder.substring(index, index + 1);
            Byte originalByte = reversHumanCodeMap.get(tempStr);
            if (originalByte != null) {
                // 说明码表上找到了一个匹配的byte 重置tempStr
                tempStr = "";
                originalBytesList.add(originalByte);
            }
            index++;
        }
        // list转成byte数组
        byte[] originalContentBytes = new byte[originalBytesList.size()];
        for (int i = 0; i < originalBytesList.size(); i++) {
            originalContentBytes[i] = originalBytesList.get(i);
        }
        return originalContentBytes;
    }

    /**
     * 将一个byte 转成一个二进制的字符串
     *
     * @param b    传入的 byte
     * @param flag 标志是否需要补高位如果是true ,表示需要补高位,如果是false表示不补, 如果是最后一个字节,无需补高位
     * @return 是该b 对应的二进制的字符串,(注意是按补码返回)
     */
    private static String byteToBitString(boolean flag, byte b) {
        //使用变量保存 b
        int temp = b; //将 b 转成 int
        //如果是正数我们还存在补高位
        if (flag) {
            temp |= 256; //按位与 256  1 0000 0000  | 0000 0001 => 1 0000 0001
        }
        String str = Integer.toBinaryString(temp); //返回的是temp对应的二进制的补码
        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }
    }

    /**
     * 将一个byte转成二进制字符串
     *
     * @param b
     * @return
     */
    public static String byteToBinaryString(byte b) {
        return Integer.toBinaryString((b & 0xFF) + 0x100).substring(1);
    }

    /**
     * 得到赫夫曼编码并压缩的方法整合
     *
     * @param content
     * @return
     */
    public static byte[] zip(byte[] content) {
        HuffmanNode huffmanTree = createHuffmanNode(content);
        getHuffmanCode(huffmanTree, "");
        return getHuffmanZipCode(content, huffmanCodeTable);
    }

    /**
     * @param content        原始文本的byte
     * @param huffmanCodeMap
     * @return
     */
    public static byte[] getHuffmanZipCode(byte[] content, Map<Byte, String> huffmanCodeMap) {
        // 1. 将原始字符串的字节数从对应的赫夫曼码表中找 然后拼接成一个新的字符串
        StringBuilder builder = new StringBuilder();
        for (byte b : content) {
            builder.append(huffmanCodeMap.get(b));
        }
        // 2.每8位封装成一个byte数字 先获取数组长度
        int len;
        if (builder.length() % 8 == 0) {
            len = builder.length() / 8;
        } else {
            len = builder.length() / 8 + 1;
        }
        byte[] zipContent = new byte[len];
        int index = 0;
        for (int i = 0; i < builder.length(); i += 8) {
            String subContent;
            if (i + 8 > builder.length()) {
                subContent = builder.substring(i);
            } else {
                subContent = builder.substring(i, i + 8);
            }
            zipContent[index++] = (byte) Integer.parseInt(subContent, 2);
        }
        return zipContent;
    }

    /**
     * 根据赫夫曼树生成赫夫曼编码 规定向左为0 向右为1
     *
     * @param node 赫夫曼树
     * @param code 原始赫夫曼编码
     * @return
     */
    public static void getHuffmanCode(HuffmanNode node, String code) {
        if (node != null) {
            // 非数据节点
            if (node.data == null) {
                // 向左递归
                // 向右递归
                getHuffmanCode(node.left, code + "0");
                getHuffmanCode(node.right, code + "1");
            } else {
                huffmanCodeTable.put((byte) node.data, code);
            }
        }
    }

    /**
     * 将传入的字符串 拆分成byte 统计 每个byte 出现的次数然后生成一颗赫夫曼树
     *
     * @param contentBytes
     * @return
     */
    public static HuffmanNode createHuffmanNode(byte[] contentBytes) {
        if (null == contentBytes || contentBytes.length == 0) {
            return null;
        }
        // 1. 字符出现次数统计
        Map<Byte, Integer> wordCount = new HashMap<>();
        for (byte b : contentBytes) {
            if (wordCount.get(b) == null) {
                wordCount.put(b, 1);
            } else {
                wordCount.put(b, wordCount.get(b) + 1);
            }
        }
        // 2. 将统计出来的字符串转成一个Node集合中用于生成 赫夫曼树
        List<HuffmanNode> huffmanNodes = new ArrayList<>();
        for (Byte key : wordCount.keySet()) {
            huffmanNodes.add(new HuffmanNode(key, wordCount.get(key)));
        }
        // 3. 将Node集合构建成一颗赫夫曼树
        while (huffmanNodes.size() > 1) {
            Collections.sort(huffmanNodes);
            HuffmanNode leftNode = huffmanNodes.get(0);
            HuffmanNode rightNode = huffmanNodes.get(1);
            HuffmanNode parent = new HuffmanNode(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;
            huffmanNodes.remove(leftNode);
            huffmanNodes.remove(rightNode);
            huffmanNodes.add(parent);
        }
        return huffmanNodes.get(0);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值