ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-05-02 10:04:42  阅读:165  来源: 互联网

标签:解压 编码 bytes value Node key byte 霍夫曼 public


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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有