2021-09-07

赫夫曼树编码思路分析:(以一串字母举例)
1) 分析每个字母出现的次数, 空格等也算. 以出现的次数作为权值, 建立赫夫曼树
2) 定义向左路径为0, 向右路径为1(由于编码的字符都是叶子节点, 所以不会出现二义性)
赫夫曼压缩后的数据解思路:
1) 将 huffmanCodeByte数组, 重新先转成字符串, 1010100110111101111010…这样的形式
即,压缩文件对应的二进制的字符串
2) 创建个新的map表, 将赫夫曼编码表反转, 再通过get()方法, 找到对应的value值, 将value值放入数组中返回即可得到需要的源文件
编码代码实现:
在这里调用了count, 在后面的解码程序中用到

public class HuffmanCode {
    static int count = 0;// 用于判断最后一位的编码省略了几个0!!!(解压时用)
    /**
     * 生成赫夫曼树对应的赫夫曼编码!!!
     * 思路:
     * 1) 将赫夫曼编码表存放在Map<Byte,String> 形式如下
     * 32->01,97->100,100->11000.....等等
     * 2) 在生成赫夫曼编码表时候, 需要去拼接路径, 所以需要创建一个StringBuilder,
     * 存储某个叶子结点的路径
     */
    static Map<Byte, String> huffmanCodes = new HashMap<>();
    static StringBuilder stringBuilder = new StringBuilder();

    // 使用一个方法, 将压缩的方法封装起来, 便于调用
    public static byte[] huffmanZip(String data) {
        // 1.将原始数据转为byte数据
        byte[] bytes = data.getBytes();
        // 2.获取出现过的字节, 以及字节出现过的次数
        List<Node1> list = getList(bytes);// 其中list存储的都是node1节点类的对象
        // 3.根据list创建赫夫曼树
        Node1 huffmanTree = createHuffmanTree(list);
        // 4.生成此赫夫曼树对应的赫夫曼编码
        Map<Byte, String> huffmanCodes = getCodes(huffmanTree);
        // 5.调用最后的压缩方法, 将字符串按照生成的赫夫曼编码对应的数值封装在一个byte[]中
        byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
        return huffmanCodeBytes;
    }

    /**
     * 编写一个方法, 将一个字符串对应的byte数组, 通过生成的 赫夫曼编码表,
     * 返回一个赫夫曼编码压缩后的byte数组
     *
     * @param bytes        此时原始字符串对应的byte数组
     * @param huffmanCodes 生成的赫夫曼编码表
     * @return 赫夫曼处理后的byte[]
     * (重要)举例:
     * 按照上面的赫夫曼编码,我们的"i like like like java do you like a java"
     * 字符串对应的编码为 (注意这里我们使用的无损压缩):
     * 10101001101111011110100110111101111010011011110111101000011
     * 00001110011001111000011001111000100100100110111101111011100100001100001110
     * =>对应的byte[] huffmanCodeBytes,即8位对应一个byte放入byte[]中
     * huffmanCodeBytes[0] = 10101001(由首位对应负数,且是补码)=>[推导 10101000 -1=>10100111(反码)=>11011000=>-88]
     */
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
        // 1.利用huffmanCodes将 bytes转变成赫夫曼编码对应的字符串
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(huffmanCodes.get(b));
        }
        System.out.println(sb);
        // 2.将sb对应的字符串转化成byte数组
        // 统计返回的byte[] huffmanCodeBytes的长度
//        int len;
//        if (sb.length()%8==0)
//            len = sb.length()/8;
//        else
//            len = sb.length()/8+1;
        int len = (sb.length() + 7) / 8;
        // 3.创建个存储压缩后的byte数组
        byte[] huffmanCodeBytes = new byte[len];
        // 因为是每八位对应一个byte, 所以步长为8
        for (int i = 0, j = 0; i < sb.length(); i += 8, j++) {
            String strByte;
            if (i + 8 > sb.length()) {// 到最后且不够8位
                strByte = sb.substring(i);
                /*
                 * 此时需要判断最后一位省略了几个0(解码时用到)
                 * 不然以0111举例, 转成十进制后是7
                 * 在解码时7转成2进制就变为了111,导致出现异常之类的情况
                 */
                while (Integer.parseInt(strByte.substring(count, count + 1)) == 0) {
                    // 当一个字母权值较大时, 且出现在最后一位时, 对应的路径可能都是0
                    // 所以要加个break语句
                    if (count + 1 == strByte.length()) {
                        count++;
                        break;
                    }
                    count++;
                }
            } else
                strByte = sb.substring(i, i + 8);
            huffmanCodeBytes[j] = (byte) Integer.parseInt(strByte, 2);
        }

        return huffmanCodeBytes;
    }

    // 包装getCodes()
    private static Map<Byte, String> getCodes(Node1 root) {
        if (root == null)
            return null;
        getCodes(root, "", stringBuilder);
        return huffmanCodes;
    }

    /**
     * 将传入的node节点的所有叶子节点的赫夫曼编码得到, 并放入到HuffmanCodes中
     *
     * @param node          传入结点, 默认从根结点
     * @param code          路径: 左子结点是0, 右子结点是1
     * @param stringBuilder 用于拼接路径
     */
    private static void getCodes(Node1 node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        // 将code传入stringBuilder2中
        stringBuilder2.append(code);
        if (node != null) {
            if (node.data == null) {// 非叶子结点
                getCodes(node.left, "0", stringBuilder2);
                getCodes(node.right, "1", stringBuilder2);
            } else {// 说明是叶子结点, 表示找到了某个叶子结点的最后
                huffmanCodes.put(node.data, stringBuilder2.toString());
            }
        }
    }

    /**
     * 接收一个字节数组, 返回一个list, 其中存放出现过的字节, 以及出现过的子节的个数
     */
    private static List<Node1> getList(byte[] bytes) {
        // 1.创建一个ArrayList
        ArrayList<Node1> node1s = new ArrayList<>();
        // 2.遍历bytes, 统计并存储每一个bytes出现的次数
        HashMap<Byte, Integer> map = new HashMap<>();
        for (byte aByte : bytes) {
            // 说明之前没存储过
            map.merge(aByte, 1, Integer::sum);
        }
        // 3.把每个键值对转成node对象, 并假如集合中
        for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
            node1s.add(new Node1(entry.getKey(), entry.getValue()));
        }
        return node1s;
    }

    // 创建约瑟夫树
    private static Node1 createHuffmanTree(List<Node1> nodes) {
        while (nodes.size() > 1) {
            Collections.sort(nodes);
            Node1 leftNode = nodes.get(0);
            Node1 rightNode = nodes.get(1);
            Node1 node = new Node1(null, leftNode.weight + rightNode.weight);
            node.left = leftNode;
            node.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(node);
        }
        return nodes.get(0);
    }
}

解码代码实现:

public class HuffmanUncompress {
    public static void main(String[] args) {
        byte[] decode = decode();
        System.out.println(new String(decode));
    }
    // 赫夫曼压缩文件对应的编码表
    static Map<Byte, String> huffmanCodes = HuffmanCode.huffmanCodes;

    // 将方法封装在一起, 传入压缩后的数据就可以直接解压
    public static byte[] decode() {
        // 1.因为没有压缩后的文件, 创建一个压缩后的文件
        String content = "i lssssss";
        byte[] codes = HuffmanCode.huffmanZip(content);
        // 2.将压缩后的文件转为String字符串
        // 即还原到1010101000这样的二进制形式
        String code = bytesToString(codes);
        // 3.通过这个二进制String字符串
        byte[] decode = decode(huffmanCodes, code);
        return decode;
    }

    /**
     * 将一个二进制转变为string字符串
     * @param bytes 赫夫曼编码得到的数组
     * @return 原来的字符串对应的数组
     */
    private static String bytesToString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            int temp = bytes[i];
            boolean flag = (i + 1) < bytes.length || temp < 0;
            if (flag) {
                temp |= 256;
                String str = Integer.toBinaryString(temp);
                sb.append(str.substring(str.length() - 8));
            } else {
                String str = Integer.toBinaryString(temp);
                // 此处是重点, 由于太笨之前被这里卡了半天才想明白-.-
                for (int j = 0; j < HuffmanCode.count; j++) {
                    sb.append(0);
                }
                sb.append(str);
            }
        }
        return new String(sb);
    }

    /**
     * 先创建一个map, 倒序存放赫夫曼编码表,
     * 再通过新的map将字符串中的数值循环匹配
     * 直到匹配成功添加到StringBuilder对象中
     * 返回StringBuilder对象
     */
    private static byte[] decode(Map<Byte, String> huffmanCodes, String codes) {
        Map<String, Byte> reverseHuffmanCodes = new HashMap<>();
        for (Map.Entry<Byte, String> byteStringEntry : huffmanCodes.entrySet()) {
            reverseHuffmanCodes.put(byteStringEntry.getValue(), byteStringEntry.getKey());
        }
        // 创建一个list用于存放数据
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < codes.length(); ) {
            int count = 1;// 计数器
            boolean flag = true;
            Byte b = null;
            while (flag) {
                String key = codes.substring(i, i + count);
                b = reverseHuffmanCodes.get(key);
                if (b == null) {
                    count++;
                } else {
                    flag = false;
                }
            }
            i += count;// i移动到count
            list.add(b);
        }
        byte[] code = new byte[list.size()];
        for (int i = 0; i < code.length; i++) {
            code[i] = list.get(i);
        }
        return code;
    }
}
上一篇:工具类-Date


下一篇:【Java】1017 A除以B (20 分)(100ms超时)