压缩、序列化与哈希

压缩和解压缩

压缩: 把数据换一种方式来存储,以减小存储空间。

解压缩: 把压缩后的数据还原为原先的形式,以便使用。

常见的压缩算法有: DEFLATE、JPEG、MP3。

其中 DEFLATE 就是 ZIP 的压缩算法,ZIP 是我们常见的打包格式。其正式说法叫做 Archive(归档),将多个文件打包成一个文件,方便传输和存储。

我们来看看什么是数据压缩。

比如对于如下数据:

复制代码
AAAABBBCCDEEEE

我可以压缩为:4A3B2C1D4E,这种压缩算法非常粗暴,如果原始数据变为这样:

复制代码
ABCDE

压缩结果:1A1B1C1D1E 反而比原始数据大。一个优秀的压缩算法,它能在常见数据下实现高压缩率。不过,你需要知道的是不存在一种能够压缩任意数据的无损算法。对于一串完全随机的数据,经过无损压缩算法处理后,体积可能会变大。

当然对于上述的压缩结果,我们还能还原为原数据。例如 4A3B2C1D4E,我们只需根据字符的出现次数以及对应的字符,即可还原为 AAAABBBCCDEEEE

那压缩属于编码吗?

其实编码是没有一个官方标准的。通常来说,我们认为的编码是使用一套固定的规则,将数据从 A 格式转换为 B 格式,并且还能复原,不损失任何数据。这么来说,压缩就属于编码。

媒体数据的编解码

媒体数据指的是将音视频、图片等数据。

以图片的编解码为例。

图片的编码:把图片数据转成 JPG、PNG 等文件的编码格式。

图片的解码:把 JPG、PNG 等文件中的数据解析成标准的图像数据。

另外,媒体数据的压缩通常是有损压缩。它之所以可行,是因为利用了我们无法察觉到图片或音频中的所有细节。有损压缩就是通过丢弃了这些我们感知不到的数据,从而实现了更高的压缩率。MP3 就是一个典型的例子,正因为有损压缩后的数据无法还原为原数据,所以有损压缩并不算我们平常所说的编码。

序列化

在 Java 中,我们经常需要让某个类实现 Serializable 接口,使其能够序列化。

而这个序列化,其实是把处于内存中的数据对象转换成字节序列的过程。

例如:我们会将 zhangsan 这个对象转为一段连续的数据,使其能够存储到文件或是在网络上进行传输。

kotlin 复制代码
data class Human(val name: String, val age: Int, val gender: String, val mate: Human? = null)

val zhangsan = Human("张三", 18, "男", Human("王五", 20, "女"))

比如我们转为 JSON 格式。

kotlin 复制代码
{"name":"张三","age":18,"gender":"男","mate":{"name":"王五","age":20,"gender":"女"}}

也可以是这样的:

kotlin 复制代码
Human(name=张三, age=18, gender=男, mate=Human(name=王五, age=20, gender=女, mate=null))

只要将内存中的这个对象转成序列,就叫序列化。不一定非得是实现了某个接口,调用了某个方法。

反序列化就是将序列化后的字节序列重新转为内存中的对象。

为什么需要序列化?

之前也说过了,就是要让内存中的对象能够被存储、被传输。根本原因是,内存中的对象结构很复杂,可能由多个部分组成,通过引用进行关联,散落在了内存的不同地方。而往磁盘存文件或者在网络上传输,都需要一段连续的字节数据。序列化,就是将内存中这些零散的对象结构,转换成一段连续字节流的过程。

Hash

Hash 的原义是 "切碎"、"拼凑" 的意思。而在程序中,意思是哈希函数,它可以把任意数据转换成一个固定长度的字符串(哈希值)。

Hash 值可用于摘要和数字指纹。

经典算法有: MD5、SHA1、SHA256 等。

哈希值为什么需要根据特定算法得出?

因为好的哈希函数有以下特点:

  • 相同的输入,必定得到相同的输出。

  • 对于不同的输入,能够尽可能产生不同的输出,避免碰撞(冲突)。

  • 输入数据的微小变化,会导致输出具有差异。

对于不同的输入,如果它们的哈希值相同,这就叫发生了哈希碰撞。比如我们设计了一个哈希算法:hash(String s) = s.length()。使用它来计算 "hello""world" 的哈希值,发现都是 5,这就发生了一次哈希碰撞。好的哈希算法会让这种碰撞的概率变得非常低。

Hash 的实际用途主要有:

  • 数据完整性的验证

    比如对于一个重要文件,如果你计算它的哈希值与其标定的哈希值相同,那么你就可以认为这份文件未被篡改,是完整的。

  • 身份的快速匹配/数据的快速比较

    我们在重写类的 equals 方法时,通常要同时重写 hashCode 方法。这是因为 HashMap 等这类容器,在判断元素是否存在时,为了效率,会先使用 hashCode 方法进行比较。因为如果两个对象的 hashCode 值不同,那么这两个对象一定不相等 。这样可以快速排除掉大部分不同的元素。只有当 hashCode 值相同时,再使用 equals 方法做最终判断,因为 hashCode 值相同不代表两个对象就一定相等,有可能是发生了哈希碰撞。

    为什么要用 hashCode 方法,而不直接使用 equals 方法进行判断?

    这是因为 hashCode 的运算非常快,用于判断两个元素不相等,最合适不过了。

另外,目前密码的存储都是将密码的哈希值存入数据库。并不是进行明文存储,因为有泄露风险。密码进行验证时,只需将输入的密码进行 Hash,再与数据库中存的哈希值进行比对,就能够判断密码是否正确。

这样,即使数据库泄露了,被别人拿到了用户密码的哈希值,别人也无法获取到真实的密码,因为 Hash 运算是不可逆的。不过,别人也可能会使用彩虹表来破解密码。

彩虹表是将常见的密码与它们的哈希值存到数据库中,这样别人就可以通过已知的哈希值,来推导出真实的密码了。也有解决方法,在计算密码的哈希值之前,通常会给密码加上盐,也就是给密码附加上额外的数据,这个数据可以是固定的,也可以是随机的。这样,别人即使知道一些常用密码的哈希值,也是无法破解密码的,因为存入数据库的值,并不是真实密码的哈希值,而是加上了额外数据计算出的哈希值。

例如:你的真实密码是 136520。我并不会计算当前字符串的哈希值,而是会加上某个字符串,如 zxc,再计算加上后的字符串的哈希值,存入到数据库中。

最后说说 Hash 是编码吗,是加密吗?

  • Hash 并不是编码,因为它只是原数据的摘要,并不能通过它来还原数据。

  • Hash 也不是加密,MD5 常常被认为是 "不可逆加密"。但加密的定义是,使用加密算法对数据进行加密隐藏原文,然后能够使用解密算法还原数据。而 Hash 的目的是生成数据的摘要进行验证,是单向的,无法还原数据。所以 MD5 等算法并不是加密算法。

字符集

字符集:一个由整数向现实世界中的文字符号的 Map。

例如这就是一个非常简单的字符集:

kotlin 复制代码
1 => A
2 => B
3 => C

最早的字符集是 ASCII,它包含了 128 个字符,每个字符占 1 字节。然后出现了 ISO-8859-1 字符集,它对 ASCII 字符集进行了扩充,每个字符也是只占 1 字节。接着有了大名鼎鼎的 Unicode 字符集,开始对拉丁字母以外的字符有了支持,比如中文、日文等。

Unicode 字符集有两个著名的编码分支,分别是:UTF-8 和 UTF-16。

编码分支是什么意思呢?在 Unicode 字符集中,规定 U+4E2D 代表 "中" 字,而 UTF-8 和 UTF-16 的实现方式则有些不同。比如 UTF-8 可能会使用 E4 B8 AD 三个字节来代表 "中",而 UTF-16 可能会用两个字节 4E 2D 来表示。虽然它们表达的是同一个字,但占用的字节数和字节内容不同。这就是同一个字符集下的不同编码实现。

然后还有 GBK / GB2312 / GB18030 系列,它们既是字符集标准,也包含了编码规则。这是中国自研的标准,不过现在全世界最广泛使用的是 Unicode 字符集和 UTF-8 编码。

相关推荐
Kapaseker1 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android