Java实现哈夫曼编码--数据无损压缩

数据结构之哈夫曼编码—数据无损压缩

哈夫曼编码可以可以将数据无损压缩,很强大,那么我们来了解一下哈夫曼编码:

首先,哈夫曼编码的原理是利用了哈夫曼树,通过统计数据中字符出现的次数,让出现最多的字符离哈夫曼树的根结点就越近,其路径也就越近,路径就是有1和0表示。

如数据:"i like like like java do you like a java"中,出现的最多的是空格,我们通过哈夫曼树,就可以将其路径变得最短,如本来可能是1001001,使用哈夫曼树后就可能变成110了, 说到这里大家可能还是不懂,还是上面的例子,可以知道空格出现了9次,l 出现了4次;假如,本来空格用100100表示,一个空格就要6个数字表示,l 用111表示,一个 l 要3个数字表示,存储所有空格和所有 l 就需要

96+43=66
那如果使用哈夫曼树后,就会变成空格用111表示,l 用100100表示,此时存储空间就只需要
93+46=51

数据还是那些数据,没有损耗,只是换了路径表示方式,就减少了所需的存储空间,所以哈夫曼编码是无损压缩数据的首选。

好了,看到这里原理我们大概明白了,接下来是思路,还是上述例子
数据:“i like like like java do you like a java”

①获取到数据中的所有字符,分别存放如一个字符数组contentBytes中(此时字符共40个)
②统计字符数组中同一个字符出现的次数,如空格就出现了9次,将统计结果放入Map中,字符为key,出现次数为value
③先要创建一个Node类,Node中应包含data(数据),和weight(权值),还要以weight作为指标进行排序(Node类继承comparable接口)
④将每一个Map集合中的个体都变成一个Node对象,故让map中的key作为data,value作为weight,所有Node对象放入一个ArrayList中
⑤创建一颗哈夫曼树,先要对所有ArrayList中的Node对象进行排序,排序后将结点组合成树
⑥以0为左边路径,1位右边路径树中的路径,得到以字符为key,路径为value的Map集合
⑦遍历map集合,将value中的路径拼接成一个长字符串
⑧将字符串每8位为一组,每组转换为一个字符,所有字符放入一个字节数组中(次数字符共17个)

以上就是哈夫曼编码的所有步骤,有点复杂,但是不难理解
接下来是代码:no code,no 逼逼

package cn.ycl.system.other.study.leetCode;

import java.util.*;

public class HuffmanCode {
    public static void main(String[] args) {
        // 需要被编码的字符串
        String content = "i like like like java do you like a java";
        // 得到字符串的字节数组
        byte[] contentBytes = content.getBytes();
        // 尝试输出该字节数组,发现是40个十进制数字
        System.out.println(Arrays.toString(contentBytes));
//		[105, 32, 108, 105, 107, 101, 32, 108, 105, 107, 101, 32, 108,
//				105, 107, 101, 32, 106, 97, 118, 97, 32, 100, 111, 32,
//				121, 111, 117, 32, 108, 105, 107, 101, 32, 97, 32, 106,
//				97, 118, 97]
        //测试是否转换成了Node对象
        List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);

        //测试一把创建的哈夫曼树
        System.out.println("以下是哈夫曼树:");
        Node root = createHuffmanTree(nodes);
        System.out.println("前序遍历");
        root.preOrder();

        //测试一把是否生成了对应的哈夫曼编码
		//根结点是没有路径的,故设置为空
        getCodes(root, "", stringBuilder);
        System.out.println("以下是Map路径:");
        //遍历map主要是为了取得key和value的值来使用,若只是单纯输出,直接输出huffmanCodes即可,这里只是为了练习map遍历,写复杂了
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }

        //测试一下路径拼接后的唯一一个长路径
        byte[] zip = zip(contentBytes, huffmanCodes);
		//得到17个十进制数字,比之前的40个十进制数字,压缩率为(40-17)/40=57.5%
        System.out.println(Arrays.toString(zip));
    }


    /**
     * 此方法的作用就是将得到的Map中的多个路径拼接成一个字符串,然后将字符串按每8位进行分割,再转成十进制
     * 其中bytes就是程序开始得到的字符数组contentBytes,huffMap就是路径的Map:huffmanCodes
     */

    private static byte[] zip(byte[] bytes, Map<Byte, String> huffMap) {
        int index = 0;
        String temp;
        StringBuilder stringBuilder = new StringBuilder();
        //遍历byte数组,将map中的路径拼接
        for (byte b : bytes) {
            stringBuilder.append(huffMap.get(b));
        }
        //判断该字符串可以组成多少个十进制数
        int len = 0;
        if (stringBuilder.length() % 8 == 0) {
            len = stringBuilder.length() / 8;
        } else {
            len = stringBuilder.length() / 8 + 1;
        }
        //将字符串截取,放入字符数组
        byte[] countss = new byte[len];
        for (int i = 0; i < stringBuilder.length(); i = i + 8) {

            if (i + 8 > stringBuilder.length()) {
                temp = stringBuilder.substring(i);
                countss[index] = (byte) Integer.parseInt(temp, 2);
                break;
            }
            temp = stringBuilder.substring(i, i + 8);
            //将截取的字符串转换成数字Integer.parseInt(temp,2)表示,temp的基数是二进制
            countss[index] = (byte) Integer.parseInt(temp, 2);
            index++;
        }
        return countss;
    }


    /**
     * 此方法的作用就是生成赫夫曼树对应的赫夫曼编码
     * 思路:
     * 1.将赫夫曼编码表存放在Map<Byte,String>
     * 2.在生成赫夫曼编码表时,需要去拼接路径,定义一个StringBuilder,存储某个叶子结点的路径
     */

    static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
    static StringBuilder stringBuilder = new StringBuilder();

    //其中code就是路径:规定:左子结点是0,右子结点;  stringBuilder是用于拼接路径
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2中
        stringBuilder2.append(code);
        if (node != null) {
            if (node.data == null) {//data空表示是非叶子结点
                //向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {//data不为空表示是叶子结点,一条路径完成,将stringbuilder2中的路径放入map中
                huffmanCodes.put(node.data, stringBuilder2.toString());
            }
        }
    }

    /**
     * 此方法的作用就是将字节数组转换成为List形式的node对象
     */
    private static List<Node> getNodes(byte[] bytes) {
        //1创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<Node>();

        //2遍历bytes ,统计每一个byte出现的次数-》使用map[key,value]
        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (Objects.isNull(count)) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //把每一个键值对转成一个Node对象,并加入到nodes集合中
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    /**
     * 此方法的作用就是通过List,创建对应的赫夫曼树
     */
    private static Node createHuffmanTree(List<Node> nodes) {
        //排序
        while (nodes.size() > 1) {
            Collections.sort(nodes);
            //得到前两个对象和创建父结点
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parentNode = new Node(null, leftNode.weight + rightNode.weight);
            //关联父子结点和移除子结点,加入父结点
            parentNode.left = leftNode;
            parentNode.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parentNode);
        }
        //返回最后的一个结点
        return nodes.get(0);
    }


}

//创建Node带数据和权值

package cn.ycl.system.other.study.leetCode;

public class Node implements Comparable<Node> {
	/**
	 * 存放数据本身,比如 ‘a’=>97 空格 ‘ ’==>32
	 */
    Byte data;
	/**
	 * 权值,表示字符出现的次数
	 */
	int weight;
	/**
	 * 左树
	 */
    Node left;
	/**
	 * 右树
	 */
	Node right;

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

    @Override
    public String toString() {
        return "Node [data=" + data + ", weight=" + weight + "]";
    }

	/**
	 * 实现comparable接口必须实现的方法
	 * @param o
	 * @return
	 */
	@Override
    public int compareTo(Node 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();
        }
    }

}
Java实现压缩与解压缩ZIP   import java.io.BufferedInputStream;   import java.io.BufferedOutputStream;   import java.io.File;   import java.io.FileInputStream;   import java.io.FileOutputStream;   import java.util.zip.ZipEntry;   import java.util.zip.ZipOutputStream;   public class Zip {   static final int BUFFER = 2048;   public static void main(String argv[]) {   try {   BufferedInputStream origin = null;   FileOutputStream dest = new FileOutputStream("E:\\test\\myfiles.zip");   ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(   dest));   byte data[] = new byte[BUFFER];   File f = new File("e:\\test\\a\\");   File files[] = f.listFiles();   for (int i = 0; i < files.length; i++) {   FileInputStream fi = new FileInputStream(files[i]);   origin = new BufferedInputStream(fi, BUFFER);   ZipEntry entry = new ZipEntry(files[i].getName());   out.putNextEntry(entry);   int count;   while ((count = origin.read(data, 0, BUFFER)) != -1) {   out.write(data, 0, count);   }   origin.close();   }   out.close();   } catch (Exception e) {   e.printStackTrace();   }   }   }   解压缩的   import java.io.BufferedInputStream;   import java.io.BufferedOutputStream;   import java.io.File;   import java.io.FileOutputStream;   import java.util.Enumeration;   import java.util.zip.ZipEntry;   import java.util.zip.ZipFile;   public class UnZip {   static final int BUFFER = 2048;   public static void main(String argv[]) {   try {   String fileName = "E:\\test\\myfiles.zip";   String filePath = "E:\\test\\";   ZipFile zipFile = new ZipFile(fileName);   Enumeration emu = zipFile.entries();   int i=0;   while(emu.hasMoreElements()){   ZipEntry entry = (ZipEntry)emu.nextElement();   //会把目录作为一个file读出一次,所以只建立目录就可以,之下的文件还会被迭代到。   if (entry.isDirectory())   {   new File(filePath + entry.getName()).mkdirs();   continue;   }   BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));   File file = new File(filePath + entry.getName());   //加入这个的原因是zipfile读取文件是随机读取的,这就造成可能先读取一个文件   //而这个文件所在的目录还没有出现过,所以要建出目录来。   File parent = file.getParentFile();   if(parent != null && (!parent.exists())){   parent.mkdirs();   }   FileOutputStream fos = new FileOutputStream(file);   BufferedOutputStream bos = new BufferedOutputStream(fos,BUFFER);   int count;   byte data[] = new byte[BUFFER];   while ((count = bis.read(data, 0, BUFFER)) != -1)   {   bos.write(data, 0, count);   }   bos.flush();   bos.close();   bis.close();   }   zipFile.close();   } catch (Exception e) {   e.printStackTrace();   }   }   }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

三七有脾气

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

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

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

打赏作者

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

抵扣说明:

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

余额充值