zoukankan      html  css  js  c++  java
  • 赫夫曼编码

    赫夫曼编码与解码

    ●赫夫曼编码也翻译为哈夫曼编码(HuffmanCoding),又称霍夫曼编码,是一种编码方式,属于一种程序算法
    ●赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
    ●赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
    ●赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码

    赫夫曼编码原理剖析

    赫夫曼树根据排序方法不同,可能不太一样,对应的赫夫曼编码也不完全一样,但长度总是相同的


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

    赫夫曼编码压缩文件注意事项

    如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化, 比如视频,ppt等等文件
    ➢赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件)
    如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显

    package com.xudong.DataStructures;
    
    import java.io.*;
    import java.util.*;
    
    public class HuffmanCodeDemo {
        public static void main(String[] args) {
            String content = "i like like like java do you like a java";
            byte[] contentBytes = content.getBytes();
            System.out.println("原字符串长度:" + contentBytes.length);
            byte[] huffmanCodesBytes = haffmanZip(contentBytes);
            System.out.println("压缩后的结果是:" + Arrays.toString(huffmanCodesBytes) + "  长度=" + huffmanCodesBytes.length);
    
            byte[] sourceBytes = decode(huffmanCodes, huffmanCodesBytes);
            System.out.println("原来的字符串=" + new String(sourceBytes));
    
            System.out.println("------------------------------");
            //测试压缩文件
            String srcFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.jpg";
            String destFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.zip";
            zipFile(srcFile,destFile);
            System.out.println("压缩文件成功!");
    
            //测试解压文件
            String zipFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲.zip";
            String unZipFile = "C:\Users\Shinelon\Desktop\java大数据开发大纲2.jpg";
            unZipFile(zipFile,unZipFile);
            System.out.println("解压文件成功!");
    
    
            /*
            List<Node1> nodes = getNodes(contentBytes);
            System.out.println("nodes=" + nodes);
    
            System.out.println("赫夫曼树,前序遍历:");
            Node1 huffmanTreeRoot = createHuffmanTree(nodes);
            huffmanTreeRoot.preOrder();
    
            Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
            System.out.println("生成的哈夫曼编码表:" + huffmanCodes);
    
            byte[] huffmanCodeBytes = zip(contentBytes, huffmanCodes);
            System.out.println("huffmanCodeBytes=" + Arrays.toString(huffmanCodeBytes));
    
             */
        }
    
        //对文件解压
        public static void unZipFile(String zipFile,String destFile){
            ObjectInputStream ois = null;
            FileOutputStream fos = null;
            try {
                //创建文件输入流
                FileInputStream fis = new FileInputStream(zipFile);
                //创建对象输入流
                ois = new ObjectInputStream(fis);
                //读取byte数组 huffmanBytes
                byte[] huffmanBytes = (byte[]) ois.readObject();
                //读取赫夫曼编码表
                Map<Byte,String> huffmanCodes = (Map<Byte, String>) ois.readObject();
                //解码
                byte[] bytes = decode(huffmanCodes, huffmanBytes);
                //将bytes数组写入到目标文件
                fos = new FileOutputStream(destFile);
                //写入数据到destFile
                fos.write(bytes);
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        //对文件进行压缩
        /**
         * @param srcFile 原文件路径
         * @param destFile 压缩后存放的目录
         */
        public static void zipFile(String srcFile,String destFile){
            FileInputStream fis = null;
            ObjectOutputStream oos = null;
            try {
                //创建文件输入流
                fis = new FileInputStream(srcFile);
                //创建一个和源文件一样大小的byte[]
                byte[] b = new byte[fis.available()];
                //读取文件
                fis.read(b);
                //对源文件进行压缩
                byte[] huffmanBytes = haffmanZip(b);
                //创建文件输出流,存放压缩文件
                FileOutputStream fos = new FileOutputStream(destFile);
                //创建一个和文件输出流关联的ObjectOutputStream
                oos = new ObjectOutputStream(fos);
                //把赫夫曼编码后的字节数组写入压缩文件
                oos.writeObject(huffmanBytes);
                //将赫夫曼编码表写入压缩文件
                oos.writeObject(huffmanCodes);
    
            } catch (IOException e) {
                System.out.println(e.getMessage());
            } finally {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        //数据解码(解压)
        //1.将huffmanCodesBytes[-88, -65, -56, -65...]转成赫夫曼编码对应的二进制字符串"101010001011..."
        //2.将对应的二进制字符串对照赫夫曼编码表转换成原来的字符串"i like like like..."
    
        /**
         * @param huffmanCodes 赫夫曼编码表
         * @param huffmanBytes 赫夫曼编码得到的数组
         * @return 原来字符串对应的数组
         */
        private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes){
            //1.先得到huffmanBytes对应的二进制字符串 "1010100010111..."
            StringBuilder stringBuilder = new StringBuilder();
            //将byte数组转成二进制字符串
            for (int i = 0; i < huffmanBytes.length; i++) {
                byte b = huffmanBytes[i];
                //判断是否为最后一个字节
                boolean flag = (i == huffmanBytes.length - 1);
                stringBuilder.append(byteToBitString(!flag,b));
            }
    
            //将赫夫曼编码表的键值调换,进行解码
            Map<String, Byte> map = new HashMap<>();
            for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()){
                map.put(entry.getValue(),entry.getKey());
            }
    
            //存放byte
            List<Byte> list = new ArrayList<>();
            //扫描stringBuilder
            for (int i = 0; i < stringBuilder.length();) {
                int count = 1;
                boolean flag = true;
                Byte b = null;
    
                while (flag){
                    //递增的取出key
                    String key = stringBuilder.substring(i, i + count);
                    b = map.get(key);
                    if (b == null){//没有匹配到
                        count++;
                    }else {//匹配到
                        flag = false;
                    }
                }
                list.add(b);
                i += count;//i 移动到count
            }
            //此时list存放了解压后的所有字符
            byte b[] = new byte[list.size()];
            for (int i = 0; i < b.length; i++) {
                b[i] = list.get(i);
            }
            return b;
        }
    
    
    
        /**
         * @param flag 标志是否需要补高位,如果是最后一个字节,则无需补高位
         * @param b 传入的byte
         * @return 是该 b 对应补码返回的二进制字符串
         */
        private static String byteToBitString(boolean flag,byte b){
           //使用变量保存b
           int temp = b;//将b转成int
           //如果是正数,则需要补高位
           if (flag){
               temp |= 256;//按或与
           }
            String str = Integer.toBinaryString(temp);//返回的是temp对应的二进制补码
            if (flag){
                return str.substring(str.length() - 8);
            }else {
                return str;
            }
        }
    
    
        /**
         * @param bytes 原始字符串对应的字节数组
         * @return 经过赫夫曼编码处理后的字节数组
         */
        private static byte[] haffmanZip(byte[] bytes){
            List<Node1> nodes = getNodes(bytes);
            //根据nodes创建的赫夫曼树
            Node1 huffmanTreeRoot = createHuffmanTree(nodes);
            //对应的赫夫曼编码(根据赫夫曼树)
            Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
            //根据生成的赫夫曼编码,压缩得到压缩后的赫夫曼编码字节数组
            byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
            return huffmanCodeBytes;
        }
    
    
    
        /**注:是补码的形式进行编码
         * @param bytes 原始的字符串对应的byte[]
         * @param huffmanCodes 生成的赫夫曼编码map
         * @return 拼接后的编码,8位一个存储在byte中
         */
        //将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
        private static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes){
            //利用huffmanCodes将bytes转成赫夫曼编码对应的字符串
            StringBuilder stringBuilder = new StringBuilder();
            //遍历传入的字节数组
            for (byte b : bytes){
                stringBuilder.append(huffmanCodes.get(b));
            }
            //统计返回byte[] huffmanCodeBytes 长度
            int len;
            if (stringBuilder.length() % 8 == 0){
                len = stringBuilder.length() / 8;
            }else {
                len = stringBuilder.length() / 8 + 1;
            }
    
            //创建存储压缩后的byte数组
            byte[] huffmanCodeBytes = new byte[len];
            int index = 0;//记录是第几个byte
            for (int i = 0; i < stringBuilder.length(); i += 8) {
                String strByte;
                if (i + 8 > stringBuilder.length()){//不够8位
                    strByte = stringBuilder.substring(i);
                }else {
                    strByte = stringBuilder.substring(i,i + 8);
                }
                //将strByte 转成 一个 byte ,放入huffmanCodeBytes中
                huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte,2);
                index++;
            }
            return huffmanCodeBytes;
        }
    
        //重载getCodes
        private static Map<Byte,String> getCodes(Node1 root){
            if (root == null){
                return null;
            }
            //处理root的左子树
            getCodes(root.left,"0",stringBuilder);
            //处理root的右子树
            getCodes(root.right,"1",stringBuilder);
            return huffmanCodes;
        }
    
    
        //将赫夫曼编码表存放到Map中,形如 32 -> 01 , 97 -> 100
        static Map<Byte,String> huffmanCodes = new HashMap<>();
        //生成的赫夫曼编码需要拼接路径,定义StringBuilder存储某个叶子节点的路径
        static StringBuilder stringBuilder = new StringBuilder();
    
        /**
         * 功能:将传入的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){//如果node == null不处理
                //判断当前node是叶子节点还是非叶子节点
                if (node.data == null){//非叶子节点
                    //向左递归
                    getCodes(node.left,"0",stringBuilder2);
                    //向右递归
                    getCodes(node.right,"1",stringBuilder2);
                }else {//说明是叶子节点
                    //存储
                    huffmanCodes.put(node.data,stringBuilder2.toString());
                }
            }
        }
    
    
        //前序遍历的方法
        public static void preOrder(Node1 root){
            if (root != null){
                root.preOrder();
            }else {
                System.out.println("赫夫曼树为空!");
            }
        }
    
        /**
         * @param bytes 接收字节数组
         * @return 返回List形式
         */
        private static List<Node1> getNodes(byte[] bytes){
            ArrayList<Node1> nodes = new ArrayList<>();
    
            //统计每一个byte出现的次数
            HashMap<Byte, Integer> counts = new HashMap<>();
            for (byte b : bytes){
                Integer count = counts.get(b);
                if (count == null){//Map还没有这个数据时
                    counts.put(b,1);
                }else {
                    counts.put(b,count + 1);
                }
            }
            //遍历map,把每一个键值对转换成一个node对象,加入到nodes集合
            for (Map.Entry<Byte,Integer> entry : counts.entrySet()){
                nodes.add(new Node1(entry.getKey(),entry.getValue()));
            }
            return nodes;
        }
    
        //通过List创建对应的赫夫曼树
        public static Node1 createHuffmanTree(List<Node1> nodes){
    
            while (nodes.size() > 1){
                //排序
                Collections.sort(nodes);
    
                //1.取出权值最小的节点(二叉树)
                Node1 leftNode = nodes.get(0);
                //2.取出权值第二小的节点(二叉树)
                Node1 rightNode = nodes.get(1);
                //3.创建一个新的二叉树,跟节点root只取权值
                Node1 parent = new Node1(null,leftNode.weight + rightNode.weight);
                parent.left = leftNode;
                parent.right = rightNode;
                //4.从ArrayList删除处理过的二叉树
                nodes.remove(leftNode);
                nodes.remove(rightNode);
                //5.将parent加入到nodes
                nodes.add(parent);
            }
            //最后的节点就是赫夫曼树root节点
            return nodes.get(0);
        }
    }
    
    //创建Node,待处理数据和权值
    class Node1 implements Comparable<Node1>{
        Byte data;//存放数据本身,即ASCII码
        int weight;//权值,即字符出现的个数
        Node1 left;
        Node1 right;
    
        public Node1(Byte data, int weight) {
            this.data = data;
            this.weight = weight;
        }
    
        @Override
        public String toString() {
            return "Node1{" +
                    "data=" + data +
                    ", weight=" + weight +
                    '}';
        }
    
        @Override
        public int compareTo(Node1 o) {
            return this.weight - o.weight;
        }
    
        //前序遍历
        public void preOrder(){
            System.out.println(this);
            if (this.left != null){
                this.left.preOrder();
            }
            if (this.right != null){
                this.right.preOrder();
            }
        }
    }
    
  • 相关阅读:
    [转]Entity Framework 和NHibernate的区别
    NHibernate One Session Per Request简单实现
    memcache和memcached之间的区别
    Memcached 内存分配机制介绍
    linux,apache,php,mysql常用的查看版本信息的方法
    Linux查看文件夹大小
    Linux查看系统配置常用命令
    php 文件缓存(转)
    memcache在xampp下的安装
    通过改进代码将for循环遍历数组提高效率
  • 原文地址:https://www.cnblogs.com/nnadd/p/13440719.html
Copyright © 2011-2022 走看看