其他分享
首页 > 其他分享> > 文件压缩与解压-霍夫曼编码

文件压缩与解压-霍夫曼编码

作者:互联网

1.背景

面试中问到霍夫曼编码,你知道么?

2.代码

package com.ldp.structure.demo06Zip;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;

/**
 * @create 05/01 5:06
 * @description <p>
 * 需求:使用霍夫曼树实现压缩与解压功能
 * 压缩
 * 0.统计每个字符对应了个数,并以节点的形式存放,List<Node>
 * 如:d:1,表示d字母出现1次,使用节点存放为  Node{key=d,value=1}
 * a:5,表示a字母出现5次,使用节点存放为  Node{key=a,value=5}
 * <p>
 * 1.将上一步中的List<Node>转为霍夫曼树
 * 2.将上一步中的霍夫曼树转变为霍夫曼编码表Map<bity,String> map
 * 如:d=11000,转为 map.put(d,"11000")
 * 如:a=100,转为 map.put(a,"100")
 * 3.使用上一步中的霍夫曼表,对"i like like like java do you like a java",
 * 逐个翻译,生成霍夫曼编码后的数据,即霍夫曼表对应的字符串,如:"101010001011...."
 * <p>
 * 4.对上一步中的霍夫曼编码"101010001011....",每8位转为一个bite,最后生成byte[]数组
 * Integer.parseInt("10001100",2);表示传入一个二进制的字符串转为对应的byte
 * </p>
 */
public class Test01 {
    /**
     * 定义一个全局的霍夫曼编码
     */
    private Map<Byte, String> huoFuManCodeMap = new HashMap<>();

    /**
     * 测试
     * 字符串压缩与解压
     */
    @Test
    public void test01() {
        String str = "i like like like java do you like a java";
        byte[] bytes2 = str.getBytes();
        // 压缩
        byte[] bytes = myZip(bytes2);
        // 解码
        byte[] bytes1 = myUnZip(bytes, huoFuManCodeMap);
        System.out.println("解码后=>" + new String(bytes1));
    }

    /**
     * 测试
     * 文件压缩与解压
     */
    @Test
    public void test02() throws Exception {
        // 文件压缩
        myFileZip("F:\\test\\t1.txt", "F:\\test\\t1.zip");
        System.out.println("文件压缩完成...");
        // 解压文件
        myFileUnzip("F:\\test\\t1.zip", "F:\\test\\t2.txt");
        System.out.println("文件解压完成...");
    }

    /**
     * 文件解压
     *
     * @param srcFileZipPath 需要解压的文件地址
     * @param dstFilePath    解压后的文件地址
     */
    public void myFileUnzip(String srcFileZipPath, String dstFilePath) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(srcFileZipPath);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        // 读取压缩后的字节数组
        byte[] bytesZip = (byte[]) objectInputStream.readObject();
        // 读取霍夫曼翻译码
        Map<Byte, String> codes = (Map<Byte, String>) objectInputStream.readObject();
        // 解压得到原文件字节数组
        byte[] bytes = myUnZip(bytesZip, codes);
        // 输出
        FileOutputStream fileOutputStream = new FileOutputStream(dstFilePath);
        fileOutputStream.write(bytes);
    }

    /**
     * 文件压缩
     *
     * @param srcFilePath 原文件地址
     * @param dstFilePath 压缩后的文件地址
     */
    public void myFileZip(String srcFilePath, String dstFilePath) throws Exception {
        // 创建文件输入流
        FileInputStream fileInputStream = new FileInputStream(srcFilePath);
        // 创建与文件一样大小的字节数组
        byte[] bytes = new byte[fileInputStream.available()];
        // 读取文件到字节数组
        fileInputStream.read(bytes);
        // 压缩文件,得到压缩后的文件字节数组
        byte[] bytesZip = myZip(bytes);
        // 创建文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream(dstFilePath);
        // 获取文件输出对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        // 将压缩后的 字节数组输出到文件
        objectOutputStream.writeObject(bytesZip);
        // 将用到的编码文件写入压缩文件
        objectOutputStream.writeObject(huoFuManCodeMap);
        //关闭资源
        objectOutputStream.close();
        fileOutputStream.close();
        fileInputStream.close();
    }

    public byte[] myZip(byte[] bytes) {
        // 0.统计每个字符对应了个数,并以节点的形式存放,List<Node>
        List<Node> nodes = strToList(bytes);
        // 1.将上一步中的List<Node>转为霍夫曼树
        Node rootNode = listToTree(nodes);
        //  preList(rootNode);
        // 2.将上一步中的霍夫曼树转变为霍夫曼编码表Map<bity,String> map
        Map<Byte, String> huoFuManCode = getHuoFuManCode(rootNode, "", new StringBuilder());
        // 3.使用上一步中的霍夫曼表,对"i like like like java do you like a java",逐个翻译,生成霍夫曼编码后的数据,即霍夫曼表对应的字符串,如:"101010001011...."
        String huoHuManStr = strToHuoHuManStr(bytes, huoFuManCode);
        // 4.对上一步中的霍夫曼编码"101010001011....",每8位转为一个bite,最后生成byte[]数组
        // bytes=>[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]
        byte[] bytesResult = huoHuManStrToByte(huoHuManStr);
        return bytesResult;
    }

    /**
     * 解压
     *
     * @param bytes
     * @param huoFuManCode
     * @return
     */
    public byte[] myUnZip(byte[] bytes, Map<Byte, String> huoFuManCode) {
        String huoFuManStr = bytToHuoFuManStr(bytes);
        byte[] bytes1 = huoFuManStrToByte(huoFuManStr, huoFuManCode);
        return bytes1;
    }

    /**
     * 霍夫曼字符串转变为byte数组
     *
     * @param huoFuManStr
     * @param huoFuManCode
     */
    public byte[] huoFuManStrToByte(String huoFuManStr, Map<Byte, String> huoFuManCode) {
        Map<String, Byte> huoFuManDecode = new HashMap<>();
        // huoFuManCode的key 与 value 反转
        for (Byte aByte : huoFuManCode.keySet()) {
            huoFuManDecode.put(huoFuManCode.get(aByte), aByte);
        }
        List<Byte> list = new ArrayList<>();
        for (int i = 0; i < huoFuManStr.length(); ) {
            int count = 1;
            while (true) {
                String key = huoFuManStr.substring(i, i + count);
                Byte aByte = huoFuManDecode.get(key);
                if (aByte == null) {
                    count++;
                } else {
                    list.add(aByte);
                    i = i + count;
                    break;
                }
            }
        }
        byte[] bytes = new byte[list.size()];
        for (int i = 0; i < list.size(); i++) {
            bytes[i] = list.get(i);
        }
        return bytes;
    }


    /**
     * 将一个byte 数组[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]转为二进制字符串"101010001011...."
     *
     * @param bytes
     * @return
     */
    public String bytToHuoFuManStr(byte[] bytes) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String string;
            if (i == (bytes.length - 1)) {
                string = byteToString(bytes[i], false);
            } else {
                string = byteToString(bytes[i], true);
            }
            stringBuilder.append(string);
        }
        return stringBuilder.toString();
    }

    /**
     * 将一个byte [-88]转为二进制"10101000"
     *
     * @param b
     * @param flag
     */
    public String byteToString(byte b, boolean flag) {
        int temp = b;
        if (flag) {
            temp |= 256;
        }
        String binaryString = Integer.toBinaryString(temp);
        if (flag) {
            binaryString = binaryString.substring(binaryString.length() - 8);
        }
        return binaryString;
    }

    /**
     * 将霍夫曼字符串(可以看做是一个二进制字符串),如:"0001011..."按照每8位一个byte转为byte[]
     *
     * @param huoHuManStr
     * @return
     */
    public byte[] huoHuManStrToByte(String huoHuManStr) {
        int length;
        if (huoHuManStr.length() % 8 == 0) {
            length = huoHuManStr.length() / 8;
        } else {
            length = huoHuManStr.length() / 8 + 1;
        }
        byte[] bytes = new byte[length];
        // huoHuManStr 按照每8位截取
        int startIndex = 0;
        for (int i = 0; i < bytes.length; i++) {
            String value;
            if (startIndex + 8 > huoHuManStr.length()) {
                value = huoHuManStr.substring(startIndex);
            } else {
                value = huoHuManStr.substring(startIndex, startIndex + 8);
            }
            bytes[i] = (byte) Integer.parseInt(value, 2);
            startIndex += 8;
        }
        return bytes;
    }

    /**
     * 根据霍夫曼编码表转为霍夫曼对应的字符串
     *
     * @param bytes        原文,如"i like like like java do you like a java"
     * @param huoFuManCode 是一个翻译表:32=0001011, 97=0001011100..
     * @return 返回对应字符串:0001011100100010110000001011100100010....
     */
    public String strToHuoHuManStr(byte[] bytes, Map<Byte, String> huoFuManCode) {
        StringBuffer huoHuManStr = new StringBuffer();
        for (byte aByte : bytes) {
            String value = huoFuManCode.get(aByte);
            huoHuManStr.append(value);
        }
        return huoHuManStr.toString();
    }

    /**
     * @param node    当前处理的节点
     * @param code    0表示左边线路,1表示右边线路
     * @param strCode 原来已经走过的线路
     * @return
     */
    public Map<Byte, String> getHuoFuManCode(Node node, String code, StringBuilder strCode) {
        if (node == null) {
            return huoFuManCodeMap;
        }
        StringBuilder strCodeNew = new StringBuilder(strCode);
        //将code 加入到 stringBuilder2
        strCodeNew.append(code);
        if (node != null) {
            if (node.getKey() == null) {
                getHuoFuManCode(node.getLeftNode(), "0", strCodeNew);
                getHuoFuManCode(node.getRightNode(), "1", strCodeNew);
            } else {
                huoFuManCodeMap.put(node.getKey(), strCodeNew.toString());
            }
        }
        return huoFuManCodeMap;
    }

    /**
     * 转为哈夫曼树
     *
     * @param nodes
     */
    public Node listToTree(List<Node> nodes) {
        while (nodes.size() > 1) {
            Collections.sort(nodes);
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parentNode = new Node(leftNode.getValue() + rightNode.getValue());
            parentNode.setLeftNode(leftNode);
            parentNode.setRightNode(rightNode);
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parentNode);
        }
        return nodes.get(0);
    }

    public List<Node> strToList(byte[] bytes) {
        Map<Byte, Integer> map = new HashMap<>();
        // a:5,表示a字母出现5次,使用节点存放为  Node{key=a,value=5}
        for (byte b : bytes) {
            byte key = b;
            Integer value = map.get(key);
            if (value == null) {
                map.put(key, 1);
            } else {
                map.put(key, value + 1);
            }
        }
        List<Node> list = new ArrayList<>();
        for (byte key : map.keySet()) {
            list.add(new Node(key, map.get(key)));
        }
        return list;
    }

    public void preList(Node node) {
        if (node == null) {
            return;
        }
        System.out.println(node);
        if (node.getLeftNode() != null) {
            preList(node.getLeftNode());
        }
        if (node.getRightNode() != null) {
            preList(node.getRightNode());
        }
    }
}

/**
 * 节点
 */
class Node implements Comparable<Node> {
    /**
     * a:5,表示a字母出现5次,使用节点存放为  Node{key=a,value=5}
     */
    private Byte key;
    // value表示权重,即字母出现的次数
    private Integer value;
    private Node rightNode;
    private Node leftNode;

    public Node(Integer value) {
        this.value = value;
    }

    public Node(Byte key, Integer value) {
        this.key = key;
        this.value = value;
    }

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

    @Override
    public String toString() {
        return "Node{" +
                "key=" + key +
                ", value=" + value +
                '}';
    }

    public Byte getKey() {
        return key;
    }

    public void setKey(Byte key) {
        this.key = key;
    }

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    public Node getRightNode() {
        return rightNode;
    }

    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }

    public Node getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }
}

 

完美!

标签:解压,编码,bytes,value,Node,key,byte,霍夫曼,public
来源: https://www.cnblogs.com/butingxue/p/16215114.html