Scala+HuffmanCoding实现无损压缩

本文介绍了如何使用Scala实现基于Huffman编码的无损数据压缩。通过字节数组、前缀编码和哈夫曼树的概念,详细阐述了数据压缩的步骤,并展示了从字符串到哈夫曼编码的过程,最后提到了文件压缩的应用。
摘要由CSDN通过智能技术生成

  数据压缩是指在不丢失有用信息的前提下,缩减数据量以减少存储空间,提高其传输、存储和处理效率,或按照一定的算法对数据进行重新组织,减少数据的冗余和存储的空间的一种技术方法。数据压缩包括有损压缩和无损压缩。

  这篇文章将从0到1的告诉大家如何实现下图所示的字符串文本压缩与文件压缩。
压缩案例
在这里插入图片描述

完成数据压缩很简单,只需要知道下面几个知识点:

  • 二进制
  • 数组
  • 前缀编码
  • Huffman tree
  • Huffman coding

下面进入正题:

什么是字节(Byte)数组?

  在 scala 中每个 Byte 都是由八位二进制补码组成的,这样的 Byte 组成的数组就是字节数组。字节也是计算机中数据存储的基本单位。

一个实现压缩的小案例:

  字符串 “abcd” 中每个字符对应的 Ascii 码是 a->97 b->98 c->99 d->100,这个字符串字符串转化为字节数组就是 Array[97, 98, 99, 100]
转为二进制补码的对应关系:
97->01100001 98->01100010 99->01100011 100->01100100

  同理,字符串 “aaaabbbccd” 对应的字节数组就是 Array[97,97,97,97,98,98,98,99,99,100] 在计算机底层其实就是 01100001011000010110000101100001011000100110001001100010011000110110001101100100
  所以这个字符串 “aaaabbbccd” 的大小为10个字节。
  这里我们重新做一个编码,把上边字符串 “aaaabbbccd” 中每个字符出现的次数做个排序,也就是 a:4 b:3 c:2 d:1
我们按照排序先后,分别用二进制 0,1,10,11 来表示 a,b,c,d
那么,字符串 “aaaabbbccdd” 就变成了 0000111101011,这里存储大小才用了12个bit,不到2个字节的大小,这其实就是一个数据压缩。
  问题来了,上边虽然实现了数据压缩,但是,我们无法还原,因为编码中 0000111101011 标黄的 0 我们不能确定它是 a 还是 c, 里面的 1 我们也不知道他们是单独的 1 还是 11 ,这种字符编码存在可能本身为其他字符编码前缀这种问题。

前缀编码

什么是前缀编码?
  字符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码, 即不能匹配到重复的编码,哈夫曼编码(Huffman coding) 就是这种 前缀 编码。
  用这种编码实现的数据压缩,我们就可以解决数据压缩还原(逆向编码即可)的问题。

哈夫曼树(Huffman Tree

在谈论哈夫曼编码前,我们先了解什么是哈夫曼树?

  • 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的 带权路径长度 (wpl: weighted path length) 达到最小,称这样的 二叉树最优二叉树 ,也称为 哈夫曼树(Huffman Tree)
  • 赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

如图所示:
Huffman tree
下面我们用代码来实现创建一个 Huffman Tree

  1. 创建一个节点类;这里 Node 需要继承 Comparable ,因为 Node 必须是可排序的。
    节点类
  2. 构建 Huffman Tree;这里需要注意的地方是,scalaListBuffer 是才是可变长 ListList 是不可变的。
    构建 Huffman Tree
哈夫曼编码(Huffman Coding

什么是哈夫曼编码?
  哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。

哈夫曼编码表
  给定字符串 "aaaabbbccd" ,根据给定字符串,我们以字符出现次数为权构建哈夫曼树;根据哈夫曼树,给各个字符规定编码,向左的路径为 0 ,向右的路径为 1 。 编码如图所示:
huffman编码表
  有了这个编码表,我们可以把给定字符串 "aaaabbbccd" 编码为 "0000101010111111110" 。比起原来的编码 "01100001011000010110000101100001011000100110001001100010011000110110001101100100" 减少了不少的空间,之前是 10 个字节,通过哈夫曼编码后只用了不到 3 个字节

重点来了!!!

如何把字节数组变成哈夫曼编码后的字节数组?
  1. 获取字节数组,遍历字节数组,以每个字节信息为 Node 节点的数据信息(data),字节出现次数为权(weight),构建一个 ListBuffer
  2. 根据 ListBuffer 里面的元素(也就是Node节点)构建哈夫曼树
  3. 根据哈夫曼树生成哈夫曼编码表
  4. 根据哈夫曼编码表,把原字节数组编码成哈夫曼编码后的字节数组,完成数据压缩

我们现在用下边这个字符串来走一遍过程
“Only if you asked to see me, our meeting would be meaningful to me”

  1. 先把字符串变成字节数组
val content = "Only if you asked to see me, our meeting would be meaningful to me"
val bytes = content.getBytes()
for (b <- bytes) print(b + " ")

// 遍历bytes:
/*
79 110 108 121 32 105 102 32 121 111 117 32 97 115 107 101 100 32 116 111 32 115 101 101 32 109 101 44 32 111 117 114 32 109 101 101 116 105 110 103 32 119 111 117 108 100 32 98 101 32 109 101 97 110 105 110 103 102 117 108 32 116 111 32 109 101
*/
  1. 使用字节数组构建 ListBuffer,ListBuffer 中元素为 Node,Node 中 data 为字节信息,weight(权) 为每个字节出现的次数,也就类似做一个word count 的功能
    字节数组->List
val list = getNodes(contentBytes)
for (x <- list) print(x.data + "->" + x.weight + "; ")
// 这里遍历结果为:
/*
32->13; 97->2; 98->1; 100->2; 101->9; 102->2; 103->2; 105->3; 107->1; 44->1; 108->3; 109->4; 110->4; 79->1; 111->5; 114->1; 115->2; 116->3; 117->4; 119->1; 121->2;
*/
  1. 根据 ListBuffer 里面的元素(也就是Node节点)构建哈夫曼树,这里返回的是哈夫曼树的 root 节点
    构建哈夫曼树
  2. 根据哈夫曼树生成哈夫曼编码表,编码表用一个可变Map来存储
    生成哈夫曼编码表
val list = getNodes(contentBytes)
val tree = createHuffmanTree(list)
val map = getHuffmanCodeTab(tree)
for ((k, v) <- map) print(k + "->" + v + "; ")
// 这里遍历后得到
/*
32->00; 97->01110; 98->110000; 100->01111; 101->101; 102->10000; 103->10001; 105->11101; 107->110001; 44->110010; 108->11110; 109->0100; 110->0101; 79->110011; 111->1101; 114->111000; 115->10010; 116->11111; 117->0110; 119->111001; 121->10011; 
*/
  1. 根据哈夫曼编码表,把原字节数组编码成哈夫曼编码后的字节数组,完成数据压缩
    经过哈夫曼编码编码后的数组
val huffmanBytes = enCode(bytes, map)
for (b <- huffmanBytes) print(b + " ")
// 这里遍历将得到
/*
-51 125 51 -80 39 -84 58 88 -41 -97 -46 86 -119 114 53 -72 18 -33 -11 98 115 -83 -25 -104 81 43 -105 -85 24 55 -113 -24 37 0 
*/
到这里,我们已经完成了数据的压缩

原字节数组:
79 110 108 121 32 105 102 32 121 111 117 32 97 115 107 101 100 32 116 111 32 115 101 101 32 109 101 44 32 111 117 114 32 109 101 101 116 105 110 103 32 119 111 117 108 100 32 98 101 32 109 101 97 110 105 110 103 102 117 108 32 116 111 32 109 101
编码后的数组:
-51 125 51 -80 39 -84 58 88 -41 -97 -46 86 -119 114 53 -72 18 -33 -11 98 115 -83 -25 -104 81 43 -105 -85 24 55 -113 -24 37 0

最后,我们来看看如何解码?

解码只需要根据编码流程,逆向操作即可
解码
bit转String

实现文件压缩

  哈夫曼编码是按字节来处理的,因此可以处理所有的文件(图片,文本文件,xml文件等);当然如果一个文件中内容重复数据不多,那么压缩效果就不会很明显,比如很复杂的色彩丰富的图片。
文件压缩
我们来测试一下

zipFile("F:\\src.txt", "F:\\scr.zip")

文件压缩


结束奉上源码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值