哈夫曼编码(Java)


超长预告-非战斗人员迅速撤离

9.8 哈夫曼编码

基本介绍

1)哈夫曼编码(HuffmanCoding),是一种编码方式,属于一种程序算法

2)哈夫曼编码是哈夫曼树在电讯通信中的经典的应用之一

3)哈夫曼编码广泛用于数据文件压缩,其压缩率通常在20~90%之间

4)哈夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方式,称之为最佳编码

实现步骤:

1)创建node节点类,包含data、weight、left、right

2)得到“ str”对应的byte[]数组

3)编写一个方法、将准备构建哈夫曼树的node节点放到list,统计字符串中每个字符出现的次数

4)通过list创建对应的哈夫曼树

package com.ldm.HuffmanCode;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author 梁东明
 * 2022/9/4
 * 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
 * 点击setting在Editor 的File and Code Templates 修改
 */
public class HuffmanCode {
    public static void main(String[] args) {
        String str = "hello my name is ldm";
        byte[] bytes = str.getBytes();   ///用byte[] 保存字符串
        System.out.println("bytes = " + bytes.length);  //输出字符串的长度

        List<Node> nodes = getNodes(bytes);  //不妨ctrl+单击 getNodes方法试试
        System.out.println("nodes = " + nodes);

        //测试创建的哈夫曼树
        System.out.println("前序遍历");
        Node huffmanCodeRoot = createHuffmanCode(nodes);
        preOrder(huffmanCodeRoot);

        //测试是否生成对应的哈夫曼编码
        getCodes(huffmanCodeRoot,"",strBuilder);
        System.out.println("huffmanCodeRoot = " + huffmanCodes);

    }

    /**
     * 前序遍历哈夫曼树
     *
     * @param root 根节点
     */
    private static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("该二叉树为null");
        }
    }
    //将哈夫曼编码保存在map集合中
    static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
    //拼接路径,用来存储某个叶子节点的路径
    static StringBuilder strBuilder = new StringBuilder();

    /**
     * 功能:将传入的node节点的所有叶子节点的哈夫曼编码得到,并放入到huffmanCodes集合中
     *
     * @param node       传入的节点
     * @param code       路径: 0是左子节点  1是右子节点
     * @param strBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder strBuilder){
        StringBuilder strBuilder2 = new StringBuilder(strBuilder);
        //将code拼接到strBuilder2中
        strBuilder2.append(code);
        if (node != null){ //node ==null 空树不处理
            if (node.data == null){ //新创建的父节点没有data值,即: 非叶子节点
                //向左递归
                getCodes(node.left,"0",strBuilder2);
                //向右递归
                getCodes(node.right,"1",strBuilder2);
            }else {  //上面不是叶子节点,那么这里就是叶子节点
                //表示找到叶子节点,将其保存在map集合中
                huffmanCodes.put(node.data,strBuilder2.toString());
            }

        }
    }


    /**
     * 该方法的用处就是用list集合保存node节点
     * 而且每个节点中保存了 字符串中  每个字符的ASCII码值和出现次数(还排好序了)
     * 等下创建哈夫曼编码用到这个list集合的节点
     *
     * @param bytes 传入的字符串数组
     * @return 返回含有data  、weight 的节点
     */
    private static List<Node> getNodes(byte[] bytes){
        //创建一个list集合,用来保存node节点,命名为nodes
        List<Node> nodes = new ArrayList<>();
        //创建一个map集合,用来统计每个字符出现的次数,命名为map
        //k(bytes的ASCII码值)-v(字符出现的次数)
        Map<Byte, Integer> map = new HashMap<>();
        //遍历字符串数组
        for (byte data : bytes) {

           /*
           Integer count = map.get(data);
           if ( count == null){//k(bytes的ASCII码值)-v(字符出现的次数)如果字符出现的次数为null,就将其插入map集合,
                map.put(data,1);
            }else {
                map.put(data, count+1);//如果字符出现过了,就将其出现次数+1
            }*/

            map.merge(data, 1, Integer::sum);
            //不得不感叹一下JAVA的技术迭代的真快,上面6行的代码,这里一行就能说明清楚。
            //看不懂这个merge方法的,尝试 ctrl+单击 看看JDK源码,顺便看一下全英的注解

        }
        //使用Entry遍历map集合,将每个map集合中的元素插入list集合中
        for (Map.Entry<Byte,Integer> entry: map.entrySet()) {
            //list集合里面的是node节点,所以还需要用到map的k-v来构建node节点
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        //返回list集合
        return nodes;
    }

    /**
     * 创建霍夫曼编码
     * 取出list集合最小的两个节点创建一个二叉树,
     * 把取出来的节点删除,把创建的二叉树插入list集合,循环
     * 直到list集合只剩下一个节点,退出循环,该节点就是哈夫曼树的根节点
     *
     * @param nodes 节点
     * @return {@link Node}
     */
    private static Node createHuffmanCode(List<Node> nodes){
        while ( nodes.size() > 1){
            Collections.sort(nodes);

            //取出list集合最小的两个元素
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            //构建一个二叉树,不存放字符串,只存放权值。
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            //把用过的节点移出集合
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //把新二叉树放回集合中
            nodes.add(parent);
        }
        return nodes.get(0);
    }
}
//创建带权值的节点类
class Node implements Comparable<Node>{
    Byte data;
    int weight;
    Node left;
    Node right;

    //构造器


    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

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

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if (this.left != null ){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }
}
/*
我的实现步骤:
1、创建node节点类,包含data、weight、left、right、包含前序遍历的方法
2、主方法中添加str字符串,得到str对应的byte[] 数组
3、编写一个方法将准备好的byte[] 数组中每个字符统计出现次数和按大小排序
  1)通过hashmap将byte[]数组遍历,统计每个字符出现次数并排好序
  2)把每个键值对k-v以一个Node对象加入到List集合中
4、通过3得到的list集合创建哈夫曼树
  1)取出列表最小的两个值
  2)以这两个节点创建父节点(父节点的data值为null,只有权值weight)
  3)在list集合删除那两个节点,把父节点插入list集合、循环
  4)直到list只剩下一个节点,退出循环。哈夫曼树创建完成
5、主方法中调用前序遍历方法,测试是否正确
6、生成对应的哈夫曼编码表
  1)先创建一个map集合用来保存编码
  2)再创建一个stringBuilder用来拼接编码
  3)创建一个方法,将传入的node节点的所有叶子节点的哈夫曼编码得到,
  并放入到huffmanCodes集合中
  4)测试生成的编码是否成功
 */

9.9 数据压缩

哈弗编码字节数组

//编写一个方法,将字符串对应的byte[] 数组,通过生成的哈夫曼编码表,返回一个哈夫曼编码,压缩后的byte[]

步骤:

1、利用HuffmanCodes 将 bytes 转成 哈夫曼编码对应的字符串

遍历bytes数组

 /**
     * 邮政编码
     *
     * @param bytes    原始的字符串对应的byte[] 数组,长度是20
     * @param huffmanCodes 生成的霍夫曼编码
     * @return {@link byte[]} 返回生成的哈夫曼编码处理后的byte[]数组
     * 01010001011011001111110011111110000010110000111011000111111010100110
     *///编写一个方法,将字符串对应的byte[] 数组,通过生成的哈夫曼编码表,返回一个哈夫曼编码,压缩后的byte[]
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {

        //1.利用 huffmanCodes 将  bytes 转成  赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes 数组
        for(byte b: bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }

        //System.out.println("测试 stringBuilder~~~=" + stringBuilder.toString());

        //将 "1010100010111111110..." 转成 byte[]

        //统计返回  byte[] huffmanCodeBytes 长度
        //一句话 int len = (stringBuilder.length() + 7) / 8;
        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) { //因为是每8位对应一个byte,所以步长 +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;
    }

9.10 数据解压

在这里插入图片描述

代码实现:

//完成数据的解压
    //1、重写先转成 哈夫曼编码对应的二进制字符串
    //2、哈夫曼编码对应的二进制的字符串、  ==》 对照哈夫曼编码

    //编写一个方法,完成对压缩数据的解码

    //编写一个方法,完成对压缩数据的解码
    /**
     *
     * @param huffmanCodes 赫夫曼编码表 map
     * @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));
        }
        //把字符串安装指定的赫夫曼编码进行解码
        //把赫夫曼编码表进行调换,因为反向查询 a->100 100->a
        Map<String, Byte>  map = new HashMap<String,Byte>();
        for(Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        //创建要给集合,存放byte
        List<Byte> list = new ArrayList<>();
        //i 可以理解成就是索引,扫描 stringBuilder
        for(int  i = 0; i < stringBuilder.length(); ) {
            int count = 1; // 小的计数器
            boolean flag = true;
            Byte b = null;

            while(flag) {
                //1010100010111...
                //递增的取出 key 1
                String key = stringBuilder.substring(i, i+count);//i 不动,让count移动,指定匹配到一个字符
                b = map.get(key);
                if(b == null) {//说明没有匹配到
                    count++;
                }else {
                    //匹配到
                    flag = false;
                }
            }
            list.add(b);
            i += count;//i 直接移动到 count
        }
        //当for循环结束后,我们list中就存放了所有的字符  "i like like like java do you like a java"
        //把list 中的数据放入到byte[] 并返回
        byte b[] = new byte[list.size()];
        for(int i = 0;i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;

    }


    /**
     * 将一个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;
        }
    }


9.11 文件压缩

/**
     * zip文件
     *
     * @param srcFile  目标文件
     * @param dstFile 输出文件
     */
    public static void zipFile(String srcFile, String dstFile) {

        //创建输出流
        OutputStream os = null;
        ObjectOutputStream oos = null;
        //创建文件的输入流
        FileInputStream is = null;
        try {
            //创建文件的输入流  
            is = new FileInputStream(srcFile);
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[is.available()];
            //读取文件
            is.read(b);
            //直接对源文件压缩
            byte[] huffmanBytes = huffmanZip(b);
            //创建文件的输出流, 存放压缩文件
            os = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutputStream
            oos = new ObjectOutputStream(os);
            //把 赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanBytes); //我们是把
            //这里我们以对象流的方式写入 赫夫曼编码,是为了以后我们恢复源文件时使用
            //注意一定要把赫夫曼编码 写入压缩文件
            oos.writeObject(huffmanCodes);


        }catch (Exception e) {

            System.out.println(e.getMessage());
        }finally {
            try {
                is.close();
                oos.close();
                os.close();
            }catch (Exception e) {

                System.out.println(e.getMessage());
            }
        }

    }

9.11 文件解压

//编写一个方法,完成对压缩文件的解压
	/**
	 * 
	 * @param zipFile 准备解压的文件
	 * @param dstFile 将文件解压到哪个路径
	 */
	public static void unZipFile(String zipFile, String dstFile) {
		
		//定义文件输入流
		InputStream is = null;
		//定义一个对象输入流
		ObjectInputStream ois = null;
		//定义文件的输出流
		OutputStream os = null;
		try {
			//创建文件输入流
			is = new FileInputStream(zipFile);
			//创建一个和  is关联的对象输入流
			ois = new ObjectInputStream(is);
			//读取byte数组  huffmanBytes
			byte[] huffmanBytes = (byte[])ois.readObject();
			//读取赫夫曼编码表
			Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();
			
			//解码
			byte[] bytes = decode(huffmanCodes, huffmanBytes);
			//将bytes 数组写入到目标文件
			os = new FileOutputStream(dstFile);
			//写数据到 dstFile 文件
			os.write(bytes);
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println(e.getMessage());
		} finally {
			
			try {
				os.close();
				ois.close();
				is.close();
			} catch (Exception e2) {
				// TODO: handle exception
				System.out.println(e2.getMessage());
			}
			
		}
	}
	

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

1)如果文件本身就是经过压缩处理,那么使用哈夫曼编码再压缩效率不会有明显的变化,比如视频、ppt等等文件

2)哈夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件,文本文件

3)如果一个文件中的内容、重复的数据不多,压缩效果也不会很明显。

全部代码

package com.ldm.HuffmanCode;

import javax.tools.FileObject;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static java.lang.Integer.parseInt;

/**
 * @author 梁东明
 * 2022/9/4
 * 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
 * 点击setting在Editor 的File and Code Templates 修改
 */
public class HuffmanCode {
    public static void main(String[] args) {
       /* //测试压缩文件
        String srcFile = "D:\\IOTest\\bg.jpg";
        String dstFile = "D:\\IOTest\\a\\bgm.zip";

        zipFile(srcFile,dstFile);
        System.out.println("压缩成功~~");
*/
        /*//测试解压文件
        String zipFile = "D:\\IOTest\\a\\bgm.zip";
        String distFile = "D:\\IOTest\\bgm.jpg";

        unZipFile(zipFile,distFile);
        System.out.println("解压成功~~");
*/
      /*  String str = "i like like like java do you like a java";
        byte[] bytes = str.getBytes();   ///用byte[] 保存字符串
        System.out.println("字符串的长度是 = " + bytes.length);  //输出字符串的长度

        byte[] huffmanCodeBytes = huffmanZip(bytes);
        System.out.println("压缩后的结果:"+Arrays.toString(huffmanCodeBytes) +"\n压缩后字符串的长度为:" +huffmanCodeBytes.length);

        byte[] sourceByte = decode(huffmanCodes, huffmanCodeBytes);
        System.out.println("原来的字符串 = " + new String(sourceByte));*/



/*
        List<Node> nodes = getNodes(bytes);  //不妨ctrl+单击 getNodes方法试试
        System.out.println("nodes = " + nodes);

        //测试创建的哈夫曼树
        System.out.println("前序遍历");
        Node huffmanCodeRoot = createHuffmanCode(nodes);
        preOrder(huffmanCodeRoot);

        //测试是否生成对应的哈夫曼编码
        getCodes(huffmanCodeRoot,"",strBuilder);
        System.out.println("huffmanCodeRoot = " + huffmanCodes);

        //测试
        byte[] zip = zip(bytes, huffmanCodes);
        System.out.println("Arrays.toString(zip) = " + Arrays.toString(zip));*/

        //发送HuffmanCodeBytes 数组

    }

    /**
     * //封装前面的方法,便于调用
     *
     * @param bytes 字节
     * @return {@link byte[]}
     */
    private static byte[] huffmanZip(byte[] bytes){
        List<Node> nodes = getNodes(bytes);
        //根据nodes创建哈夫曼树
        Node huffmanCodeRoot = createHuffmanTree(nodes);
        //对应的哈夫曼编码
        getCodes(huffmanCodeRoot,"",strBuilder);
        //根据生成的哈夫曼编码,压缩得到压缩后的哈夫曼编码字节数组
        byte[] huffmanCodeByte = zip(bytes, huffmanCodes);
        return  huffmanCodeByte;
    }
    //编写一个方法,完成对压缩文件的解压
    /**
     *
     * @param zipFile 准备解压的文件
     * @param dstFile 将文件解压到哪个路径
     */
    public static void unZipFile(String zipFile, String dstFile) {

        //定义文件输入流
        InputStream is = null;
        //定义一个对象输入流
        ObjectInputStream ois = null;
        //定义文件的输出流
        OutputStream os = null;
        try {
            //创建文件输入流
            is = new FileInputStream(zipFile);
            //创建一个和  is关联的对象输入流
            ois = new ObjectInputStream(is);
            //读取byte数组  huffmanBytes
            byte[] huffmanBytes = (byte[])ois.readObject();
            //读取赫夫曼编码表
            Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();

            //解码
            byte[] bytes = decode(huffmanCodes, huffmanBytes);
            //将bytes 数组写入到目标文件
            os = new FileOutputStream(dstFile);
            //写数据到 dstFile 文件
            os.write(bytes);
        } catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        } finally {

            try {
                os.close();
                ois.close();
                is.close();
            } catch (Exception e2) {
                // TODO: handle exception
                System.out.println(e2.getMessage());
            }

        }
    }


    /**
     * zip文件
     *
     * @param srcFile  目标文件
     * @param dstFile 输出文件
     */
    public static void zipFile(String srcFile, String dstFile) {

        //创建输出流
        OutputStream os = null;
        ObjectOutputStream oos = null;
        //创建文件的输入流
        FileInputStream is = null;
        try {
            //创建文件的输入流
            is = new FileInputStream(srcFile);
            //创建一个和源文件大小一样的byte[]
            byte[] b = new byte[is.available()];
            //读取文件
            is.read(b);
            //直接对源文件压缩
            byte[] huffmanBytes = huffmanZip(b);
            //创建文件的输出流, 存放压缩文件
            os = new FileOutputStream(dstFile);
            //创建一个和文件输出流关联的ObjectOutputStream
            oos = new ObjectOutputStream(os);
            //把 赫夫曼编码后的字节数组写入压缩文件
            oos.writeObject(huffmanBytes); //我们是把
            //这里我们以对象流的方式写入 赫夫曼编码,是为了以后我们恢复源文件时使用
            //注意一定要把赫夫曼编码 写入压缩文件
            oos.writeObject(huffmanCodes);


        }catch (Exception e) {

            System.out.println(e.getMessage());
        }finally {
            try {
                is.close();
                oos.close();
                os.close();
            }catch (Exception e) {

                System.out.println(e.getMessage());
            }
        }

    }



    /**完成数据的解压
     *1、重写先转成 哈夫曼编码对应的二进制字符串
     *2、哈夫曼编码对应的二进制的字符串、  ==》 对照哈夫曼编码
     *编写一个方法,完成对压缩数据的解码
     *编写一个方法,完成对压缩数据的解码
     * @param huffmanCodes 赫夫曼编码表 map
     * @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));
        }
        //把字符串安装指定的赫夫曼编码进行解码
        //把赫夫曼编码表进行调换,因为反向查询 a->100 100->a
        Map<String, Byte>  map = new HashMap<String,Byte>();
        for(Map.Entry<Byte, String> entry: huffmanCodes.entrySet()) {
            map.put(entry.getValue(), entry.getKey());
        }

        //创建要给集合,存放byte
        List<Byte> list = new ArrayList<>();
        //i 可以理解成就是索引,扫描 stringBuilder
        for(int  i = 0; i < stringBuilder.length(); ) {
            int count = 1; // 小的计数器
            boolean flag = true;
            Byte b = null;

            while(flag) {
                //1010100010111...
                //递增的取出 key 1
                if ( i+ count > stringBuilder.length()-1  && Integer.parseInt(String.valueOf(i)) == 0){
                    String key = stringBuilder.substring(i, stringBuilder.length());
                }
                String key = stringBuilder.substring(i, i+count);//i 不动,让count移动,指定匹配到一个字符
                b = map.get(key);
                if(b == null) {//说明没有匹配到
                    count++;
                }else {
                    //匹配到
                    flag = false;
                }
            }
            list.add(b);
            i += count;//i 直接移动到 count
        }
        //当for循环结束后,我们list中就存放了所有的字符  "i like like like java do you like a java"
        //把list 中的数据放入到byte[] 并返回
        byte b[] = new byte[list.size()];
        for(int i = 0;i < b.length; i++) {
            b[i] = list.get(i);
        }
        return b;

    }


    /**
     * 将一个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;
        }
    }





    /**
     * 前序遍历哈夫曼树
     *
     * @param root 根节点
     */
    private static void preOrder(Node root){
        if (root != null){
            root.preOrder();
        }else {
            System.out.println("该二叉树为null");
        }
    }

    /**
     *
     *
     * @param bytes    原始的字符串对应的byte[] 数组,长度是20
     * @param huffmanCodes 生成的霍夫曼编码
     * @return {@link byte[]} 返回生成的哈夫曼编码处理后的byte[]数组
     * 01010001011011001111110011111110000010110000111011000111111010100110
     *///编写一个方法,将字符串对应的byte[] 数组,通过生成的哈夫曼编码表,返回一个哈夫曼编码,压缩后的byte[]
    private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {

        //1.利用 huffmanCodes 将  bytes 转成  赫夫曼编码对应的字符串
        StringBuilder stringBuilder = new StringBuilder();
        //遍历bytes 数组
        for(byte b: bytes) {
            stringBuilder.append(huffmanCodes.get(b));
        }

        //System.out.println("测试 stringBuilder~~~=" + stringBuilder.toString());

        //将 "1010100010111111110..." 转成 byte[]

        //统计返回  byte[] huffmanCodeBytes 长度
        //一句话 int len = (stringBuilder.length() + 7) / 8;
        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) { //因为是每8位对应一个byte,所以步长 +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;
    }

    //将哈夫曼编码保存在map集合中
    static Map<Byte,String> huffmanCodes = new HashMap<Byte,String>();
    //拼接路径,用来存储某个叶子节点的路径
    static StringBuilder strBuilder = new StringBuilder();

    /**
     * 功能:将传入的node节点的所有叶子节点的哈夫曼编码得到,并放入到huffmanCodes集合中
     *
     * @param node       传入的节点
     * @param code       路径: 0是左子节点  1是右子节点
     * @param strBuilder 用于拼接路径
     */
    private static void getCodes(Node node, String code, StringBuilder strBuilder){
        StringBuilder strBuilder2 = new StringBuilder(strBuilder);
        //将code拼接到strBuilder2中
        strBuilder2.append(code);
        if (node != null){ //node ==null 空树不处理
            if (node.data == null){ //新创建的父节点没有data值,即: 非叶子节点
                //向左递归
                getCodes(node.left,"0",strBuilder2);
                //向右递归
                getCodes(node.right,"1",strBuilder2);
            }else {  //上面不是叶子节点,那么这里就是叶子节点
                //表示找到叶子节点,将其保存在map集合中
                huffmanCodes.put(node.data,strBuilder2.toString());
            }

        }
    }


    /**
     * 该方法的用处就是用list集合保存node节点
     * 而且每个节点中保存了 字符串中  每个字符的ASCII码值和出现次数(还排好序了)
     * 等下创建哈夫曼编码用到这个list集合的节点
     *
     * @param bytes 传入的字符串数组
     * @return 返回含有data  、weight 的节点
     */
    private static List<Node> getNodes(byte[] bytes){
        //创建一个list集合,用来保存node节点,命名为nodes
        List<Node> nodes = new ArrayList<>();
        //创建一个map集合,用来统计每个字符出现的次数,命名为map
        //k(bytes的ASCII码值)-v(字符出现的次数)
        Map<Byte, Integer> map = new HashMap<>();
        //遍历字符串数组
        for (byte data : bytes) {

           /*
           Integer count = map.get(data);
           if ( count == null){//k(bytes的ASCII码值)-v(字符出现的次数)如果字符出现的次数为null,就将其插入map集合,
                map.put(data,1);
            }else {
                map.put(data, count+1);//如果字符出现过了,就将其出现次数+1
            }*/

            map.merge(data, 1, Integer::sum);
            //不得不感叹一下JAVA的技术迭代的真快,上面6行的代码,这里一行就能说明清楚。
            //看不懂这个merge方法的,尝试 ctrl+单击 看看JDK源码,顺便看一下全英的注解

        }
        //使用Entry遍历map集合,将每个map集合中的元素插入list集合中
        for (Map.Entry<Byte,Integer> entry: map.entrySet()) {
            //list集合里面的是node节点,所以还需要用到map的k-v来构建node节点
            nodes.add(new Node(entry.getKey(),entry.getValue()));
        }
        //返回list集合
        return nodes;
    }

    /**
     * 创建霍夫曼编码
     * 取出list集合最小的两个节点创建一个二叉树,
     * 把取出来的节点删除,把创建的二叉树插入list集合,循环
     * 直到list集合只剩下一个节点,退出循环,该节点就是哈夫曼树的根节点
     *
     * @param nodes 节点
     * @return {@link Node}
     */
    private static Node createHuffmanTree(List<Node> nodes){
        while ( nodes.size() > 1){
            Collections.sort(nodes);

            //取出list集合最小的两个元素
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            //构建一个二叉树,不存放字符串,只存放权值。
            Node parent = new Node(null, leftNode.weight + rightNode.weight);
            parent.left = leftNode;
            parent.right = rightNode;

            //把用过的节点移出集合
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            //把新二叉树放回集合中
            nodes.add(parent);
        }
        return nodes.get(0);
    }
}
//创建带权值的节点类
class Node implements Comparable<Node>{
    Byte data;
    int weight;
    Node left;
    Node right;

    //构造器


    public Node(Byte data, int weight) {
        this.data = data;
        this.weight = weight;
    }

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

    @Override
    public String toString() {
        return "Node{" +
                "data=" + data +
                ", weight=" + weight +
                '}';
    }
    //前序遍历
    public void preOrder(){
        System.out.println(this);
        if (this.left != null ){
            this.left.preOrder();
        }
        if (this.right != null){
            this.right.preOrder();
        }
    }
}
/*
我的实现步骤:
1、创建node节点类,包含data、weight、left、right、包含前序遍历的方法
2、主方法中添加str字符串,得到str对应的byte[] 数组
3、编写一个方法将准备好的byte[] 数组中每个字符统计出现次数和按大小排序
  1)通过hashmap将byte[]数组遍历,统计每个字符出现次数并排好序
  2)把每个键值对k-v以一个Node对象加入到List集合中
4、通过3得到的list集合创建哈夫曼树
  1)取出列表最小的两个值
  2)以这两个节点创建父节点(父节点的data值为null,只有权值weight)
  3)在list集合删除那两个节点,把父节点插入list集合、循环
  4)直到list只剩下一个节点,退出循环。哈夫曼树创建完成
5、主方法中调用前序遍历方法,测试是否正确
6、生成对应的哈夫曼编码表
  1)先创建一个map集合用来保存编码
  2)再创建一个stringBuilder用来拼接编码
  3)创建一个方法,将传入的node节点的所有叶子节点的哈夫曼编码得到,
  并放入到huffmanCodes集合中
  4)测试生成的编码是否成功
7、编写一个方法,将字符串对应的byte[] 数组,通过生成的哈夫曼编码表,
返回一个哈夫曼编码,压缩后的byte[]
8、编写一个方法,对哈夫曼编码进行解压
  1)字节转二进制字符串
  2)将二进制转换字符串
9、对文件压缩
  1)创建一个方法,传入输入流地址、输出流地址
  2)
10、对文件的解压
 */

本次哈夫曼编码教程出自韩顺平的数据结构与算法

数据结构和算法教程,哔哩哔哩详细教程
在 115-126p.

最后,认识一下,我是小白。努力成为一名合格的程序员。期待与你的下次相遇。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
哈夫曼编码是一种常见的压缩算法,可以将一个字符串或文件转换为一个较小的二进制编码。以下是使用Java实现哈夫曼编码的示例代码: ```java import java.util.*; public class HuffmanCoding { private static class Node implements Comparable<Node> { int frequency; char character; Node left, right; public Node(char character, int frequency, Node left, Node right) { this.character = character; this.frequency = frequency; this.left = left; this.right = right; } public boolean isLeaf() { return left == null && right == null; } @Override public int compareTo(Node node) { return frequency - node.frequency; } } private static Map<Character, String> encode(String text) { // 统计字符频率 Map<Character, Integer> frequencyMap = new HashMap<>(); for (char c : text.toCharArray()) { frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); } // 构建哈夫曼树 Queue<Node> queue = new PriorityQueue<>(); for (char c : frequencyMap.keySet()) { queue.add(new Node(c, frequencyMap.get(c), null, null)); } while (queue.size() > 1) { Node left = queue.poll(); Node right = queue.poll(); queue.add(new Node('\0', left.frequency + right.frequency, left, right)); } Node root = queue.poll(); // 构建编码表 Map<Character, String> encodingMap = new HashMap<>(); buildEncodingMap(root, "", encodingMap); // 编码文本 Map<Character, String> encodedText = new HashMap<>(); for (char c : text.toCharArray()) { encodedText.put(c, encodingMap.get(c)); } return encodedText; } private static void buildEncodingMap(Node node, String code, Map<Character, String> map) { if (node.isLeaf()) { map.put(node.character, code); } else { buildEncodingMap(node.left, code + "0", map); buildEncodingMap(node.right, code + "1", map); } } public static void main(String[] args) { String text = "hello world"; Map<Character, String> encodingMap = encode(text); System.out.println("Encoding Map: " + encodingMap); StringBuilder encodedText = new StringBuilder(); for (char c : text.toCharArray()) { encodedText.append(encodingMap.get(c)); } System.out.println("Encoded Text: " + encodedText); } } ``` 在上面的示例中,我们首先统计了给定文本中每个字符的频率。然后,我们使用优先队列(最小堆)构建哈夫曼树,其中每个节点都包含一个字符、其频率以及左右子节点。接下来,我们使用递归方式构建编码表,其中每个字符都映射到一个二进制编码。最后,我们使用编码表将原始文本转换为二进制编码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梁小樽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值