Loading...
墨滴

张春成

2021/06/09  阅读:103  主题:默认主题

文件 = 内容 + 编码

文件 = 内容 + 编码

在目前主流的电脑系统中,所有的数据都可以看作一组二进制数所构成的具体的流。 如果你拿一个示波器去看,你是能够在主板的合适位置上观测到这一列数字的。 另一方面,这组数据的内容往往对应现实世界中的一张图像、或者是一段文字。 这就构成了一种两端的结构,一端是计算机要处理的物理世界,另一端是计算机的具体硬件,而把这两个具体端联系起来的,我们可以理解成是抽象的编码过程。 本文将解析这一过程。


文件 = 内容 + 编码 (之一)

  • 本文将以图像编码为例,以 PowerShell (PS)网络响应为载体,使用 Python 语言进行数据处理,试图对这一转换过程进行简要的阐释;
  • 另外,本文还将在该例子的语境下,试图对 Python 语言使用过程中,会经常遇到坑的文件编码问题之源头进行解释;
  • 通过该例子,相信读者还会涉及一个非常有趣的领域,即计算机的文字编码系统,在中文文字引入之后,面临了怎样的麻烦,及其目前补丁式的解决方法所引起的巨大兼容性问题。

如果有耐心看完,也许你会和我一样,觉得目前使用的计算机系统,并没有看上去的那么简单和呆萌可爱。

图像篇

图像的概念--认知、矩阵与对象

由于本文将以图像的编码为例进行讨论,我们不妨对图像的定义稍加讨论。

  • 以人眼的视角来说,图像是感光细胞对光线的反应,大脑的视觉皮层负责整理并认知这些光线所生成的“图像”,从中识别物体或提取丰富的语义信息。 但遗憾的是,计算机无法复现这种认知图像或语义图像;
  • 当我们在计算机的语境下说到“图像”,我们指的是电脑屏幕或画布表面上的一块由各种颜色有序排到而成的区域,我们比较习惯地先验地定义这是一个矩形区域。 这个矩阵区域就是目前我们在一般语境下所指称的图像。目前计算机所处理的图像大多也是指的是这种图像,我们不妨将之更准确地称为矩阵图像;
  • 而图像对象是计算机对矩阵图像的编码和抽象。 同一张矩阵图像可以通过不同的编码技术生成.jpg, .png, .tif等等多种类型的图像编码序列,计算机系统对图像编码进行特定方式的封装,就会生成各种各样的图像对象。 计算机所存储和操作的,就是这些图像对象。

不要觉得以上的维特根斯坦式的语义思辨是纯粹啰嗦无用的,因为经过这些分析之后,我们至少明确了对于图像的三种表达,分别是大脑认知的、假想矩阵的和计算机抽象算术的表达。 三种表达分别对应本文标题中的内容、编码和文件

对于本文来说,较为现实的意义在于,经过以上分析之后,我们可以专注于图像矩阵的分析。 那么,在这个语义框架下,我们可以适当地对“编码”的概念进行细分和延拓,而不至于与认知过程的编码概念相混淆,同时也能够允许计算机中的图像对象具有自己的编码概念。

我承认我在这里暗示了后续对另外二种编码的探索,相当于给自己挖了一个深不见底的坑。 但是现在,请大家专注于“矩阵图像”而把其他的东西暂时忘掉。

对于矩形图像来说,我们可以自然而然地使用数学中“矩阵”的概念对它进行表示。

现在你可以利用面前的显示器为例进行理解。 当打开显示设置,你可以看到类似分辨率为1920 x 1080这样的数字,它代表你的显示器在横、纵两个方向分别具有19201080个像素。 现在将上面的内容想象成一张尺寸合适的图像,那么这张图像在屏幕上就拥有和屏幕尺寸相同的物理分辨率,分辨率的单位我们称为像素,也就是说,这张图像包含 1920 x 1080个像素。 由于显示技术和计算机二进制表示的原因,每个像素的颜色可以表示为RGB三个分量,每个分量是离散的,包含 256 个等级,比如#FF0000代表红色,#00FF00代表绿色等等。

因此,我们可以安全地说,矩阵图像至少应该包含这样一个三维矩阵,比如尺寸为 3 x 1920 x 1080的矩阵,它代表1920 x 1080个像素和3个颜色分量。 当然,这里并不是说矩阵图像一定是这样的矩阵,因为我们可以轻易地给颜色空间增加一个维度,例如透明度维度,它控制着图像对其“下”图像的透视程度,这样就成为了4 x 1920 x 1080这样的矩阵。

而我们的计算机系统,为了对矩阵图像进行有效存储,需要将之进行序列化,即将原本的矩阵,按照一定的顺序“拉直”形成一列数字。 这样我们只要事先约定“拉直”的方式,就可以方便地生成数字序列与图像矩阵之间的对应关系。

至此,我们已经可以将矩阵图像等价成一列数字,之后的工作将专注于对这一列数字进行编码。这一工作将等同于对矩阵图像本身进行编码。

文件 = 内容 + 编码 (之二)

本部分接上文《文件 = 内容 + 编码 (之一)》。

至此,我们已经可以将矩阵图像等价成一列数字,之后的工作将专注于对这一列数字进行编码。……这一工作将等同于对矩阵图像本身进行编码。

本文将介绍基本的矩阵图像编码方法,并且给出一个 PS 进行网络请求图像的例子,来具体说明图像矩阵与二进制序列之间互相转换的关系。


矩阵图像的编码

一个具体的例子

由于我们之前已经完成了从“矩阵图像”到“颜色矩阵”再到“数字序列”的转换,我们可以专注于对“数字序列”进行编码。

为了提升文档的直观性,以及国内网络环境的可用性,我们采用一张来自百度知道的颜色图进行说明,图像的 URL 为 The Example jpg File[1] ,利用浏览器下载之后,会在硬盘上生成支持网络分发的图像格式,默认的文件名为 d439b6003af33a87c7ef165dca5c10385243b5f2.png

你可能已经注意到,这里出现了.jpg.png格式之间莫名其妙的转换,因此会不可避免地涉及我们在前面所提及的计算机图像对象存储和处理问题,大家可以先默默记下这一问题,暂且将之标记为 遗留问题一,我在之后的部分里会展开说明。

查看属性可知,图像大小为562 x 507,颜色位深度为 32,即 4 个字节。它意味着图像矩阵的大小应为562 x 507 x 3 x 4 个字节。 为了逐一存储这些数字,我们至少需要 3,419,208个字节,大约为 3 MB大小的存储空间。 而实际上,图像的大小为 48.9 KB。 这中间的差值就是图像矩阵编码的功劳。

下面,我们来剖析图像矩阵的编码过程。 对于图像来说,它的像素之间具有极强的规律性,比如图像的背景往往是同样颜色的大片重复,图像的纹理往往是具有分形结构的相似重复等等。 这些重复特性导致图像虽大,但从图像直方图的角度来看,表达这些像素所需要的信息量并不大。根据信息论中的压缩感知研究理论,在失真可控、甚至无损的条件下,我们可以通过采用合适的编码方式,实现图像数据的大幅压缩。 另外提一句,信息论和压缩感知也是个待填的大坑。

压缩方式可以使用基于数字序列的方式,如 Huffman 编码等;也可以使用基于内容的方式,如矢量编码等。 总之,经过编码之后,原本很长的一列数,可以对应地转换成较短的一列数。 压缩后的数字序列往往是二进制序列,单独地看这一序列的某一段内容往往并没有实际意义,但它在数学上,却实实在在地与原来的图像矩阵具有对应关系。

下面,我们将使用 PS 对该图像进行下载,并且会立即遇到一个意外问题。

基于 PS 的网络图像获取

最简单的情况下,我们可以采用如下语句进行图像下载

# Require with -OutFile Option

Invoke-WebRequest https://gss0.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D600%2C800/sign=72df17df52df8db1bc7b74623913f16c/d439b6003af33a87c7ef165dca5c10385243b5f2.jpg -OutFile a.png

这样该图像按照 -OutFile 参数的要求就会出现在名为 a.png 的文件里。 但这样做虽然能够获得我们想要的图像,却会意外地掩盖一个严重的错误解码问题,为了说明这个问题,我们不妨将网络响应单独拿出来看一下,这需要重新执行去掉存储文件参数的命令。

# Require
Invoke-WebRequest https://gss0.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D600%2C800/sign=72df17df52df8db1bc7b74623913f16c/d439b6003af33a87c7ef165dca5c10385243b5f2.jpg​

得到结果如下

The Result Reads as Below:
StatusCode        : 200
StatusDescription : OK
Content           : {137, 80, 78, 71...}
RawContent        : HTTP/1.1 200 OK
Transfer-Encoding : chunked
Connection        : keep-alive
Age               : 25
Tracecode         : 23180594962453592074053014
Ohc-Response-Time : 1 0 0 00 0
Ohc-Cache-HIT     : bj3cm96 [4], bdcmcache96 [2] Accept...
Headers           : {[Transfer-Encoding, chunked],[Connection, keep-alive], [Age, 25], [Tracecode, 2318059496245359207                    4053014]...}
RawContentLength  : 50084

稍加分析,就可以几乎立即确定,我们的目标图像是以某种形式存储在Content字段中。 然而,悲哀的是,这个137, 80, 78, 71, ...的一串数字,既不是图像矩阵,也不像是压缩后的二进制位。 事实上,它确实是压缩后的二进制位,但是属于对其进行错误解码得到的一串数字。 也就是说,要得到原始图像,我们首先需要将它纠正为正确的二进制序列,之后才能将它重新解码为原始图像。我们一步一步来。

从网络响应中提取原始图像

要纠正这个错误,首先要了解它在解码过程中犯了怎样的错误。 稍加分析(查阅 PS 中 Invoke-WebRequest 的文档)不难发现,PS 从目标 URL 所获取的是如假包换的正确二进制序列,而它明显辜负了这段序列的设计初衷。

它只是简单粗暴地顺序读取序列中的若干个字节,将它解释成对应的数字,并把这些数字按顺序排列起来。 这样原本的图像压缩序列,就被简单粗暴地解析成了一列没有任何意义的数字。 (好死不死这段序列的数字还都恰好处在 0 - 255 之间,让人忍不住想把它理解成颜色值,还好它的长度并不是 3 或图像尺寸的整数倍)

现在既然我们了解了错误的根源,我们就可以纠正它,使用 Python 可以轻易地做到这一点

# Read the File as Bytes
raw = open(fname, 'rb').read()

# Parse the Numbers
numbers = np.array([int(e) for e in raw.decode('utf-16').split()])

# Convert(Correct) the Numbers Back into the Bytes
parsedBytes = ''.join([chr(e) for e in numbers]).encode('latin1')

# The parsedBytes will be as Below
# b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x022\x00\x00\x01\xfb\x08\x06\x00\x00\x00\xd6\xc3u\xb8\x00\x00\x00\x06bKGD…… Ul\x15\xd9\xea\xd8x\x00\x00\x00\x00IEND\xaeB`\x82'"

我们可以从解析出的二进制序列看出,这几乎明显地是一个 .png 图像的,因为其中已经包括 PNG、HDR、END等关键字段,而根据 IEEE 所给出的 .png 图像编码标准,我们可以从这些字节位中,提取出一些关键信息,用来还原出其所编码的图像矩阵。 由于具体的还原过程涉及读取顺序、压缩方法、数字格式等多种因素,在现阶段,我们不妨暂时放下这些过于繁琐的细节,直接使用 Python 提供的解析工具来将该二进制序列还原为图像。

# Import the Relative Utils
from io import BytesIO
from PIL import Image

# Parseimg from the parsedBytes
img = BytesIO(parsedBytes)

# Plotthe img
Image.open(img)

此时,我们已经完成了从二进制序列解码出原始的图像矩阵的工作。

然而放下暂时的喜悦,我们重新审视编码纠正的工作,会发现 PS 操作过程中,至少暗含着两个未经考察的问题,这两个问题都是针对字节解码的问题,它们分别对应着在纠正代码中所使用的utf-16latin1编码协议。 如果我们轻易放过这两个编码问题,我们将错过 W 系统进行中文编码的重要细节,给之后的代码工作留下后患。 因此,在接下来的一篇文章,我将对此进行分析。

文件 = 内容 + 编码 (之三)

本部分接上文《文件 = 内容 + 编码 (之二)》。

我们重新审视编码纠正的工作,会发现 PS 操作过程中,至少暗含着两个未经考察的问题,这两个问题都是针对字节解码的问题,它们分别对应着在纠正代码中所使用的utf-16latin1编码协议。 ……如果我们轻易放过这两个编码问题,我们将错过 W 系统进行中文编码的重要细节,给之后的代码工作留下后患。 因此,在接下来的一篇文章,我将对此进行分析。

本文将跳出图像矩阵编码的范畴,讨论在计算机系统中较为一般的符号编码问题。 但是由于该问题过于繁杂,本文将集中讨论 Python 语言环境下的二进制编码问题,以及中文符号加入后,给原先的拉丁字母表达系统造成的麻烦。 另外,由于本部分内容可能包含过多的细节,为了避免失于细碎,我们不妨先将其中容易出现理解偏差的关键节点列写出来,再逐一作出解析。


一般二进制序列的编码

童话式的自洽逻辑及其陷阱

上一节中的图像传输过程,看上去是经过了由二进制编码到数字,再回到二进制编码的处理路线。 然而,如果这样理解,就无疑会忽略至少两个关键的技术节点,以及数字的存储和读取方式,总共三个陷阱。 我们首先把这三个陷阱在处理流程上标记出来,再做逐一介绍。

具体来说,系统获取二进制编码,对二进制编码进行切分,形成一列数字(技术节点一),这一列数字写入文件,再从文件中读取这些数字(数字存储和读取),从数字还原出最初的二进制编码(技术节点二)。

技术节点一

在这一节点中,二进制序列并没有经过任何意义上的编码。 而是单纯的二进制数字到十进制数字之间的转换。 比如形如下式的对应关系。

(DEC) 137 = (BIN) 1000 1001 = (HEX) 89

由于切分的长度为8比特,我们可以使用这样的规则,将二进制序列转换为0 - 255之间的正整数序列。 目前为止还十分简洁,但问题在于,如何将正整数列还原为原始的二进制数列,即技术节点二。

技术节点二

该节点的目的是在于如何将正整数还原为 8 位的二进制数。 遗憾的是,目前计算机系统中所使用的编码方式百花齐放,这导致了反向的解码结果并非是唯一的。 比如,

  • ASCII 编码是7比特的字符集,涵盖了英语中的绝大多数字符,编码从0127(这与本例的 8 比特编码并不兼容);
  • ISO Latin-1 是8比特的字符集,定义了256个字符。前128个字符(00000000-01111111)与 ASCII 完全一致(这也是本例中所使用的编码方式);
  • UTF-8 采用可变长度的编码,长度从14个字节,即 832 个比特位不等(这是十分容易出现问题的编码);
  • 由于除 ASCII 编码之外,几乎所有的编码都用满了一个字节,即 8 位二进制数的长度,因此都或多或少地利用到添加前导字节的技术,来标记特殊字符,用来避免潜在错误,这往往就是各种莫名其妙编码问题的由来。

在本例中,若使用 utf-8 编码对数字进行解码,我们会在值大于 127 时,在解码结果中获得形如 \xc2 的前导字节,这无疑会导致后续的解码工作。 当然这只是一个极其特殊的例子,但是它给我们标示出了一个潜在的问题,即在采用了不合适的编码规则的情况下,程序可能不会立即显示出不正常的行为,但极其可能会有不经意的 BUG 存储在系统中,十分需要程序员注意。

数字存储和读取

最后,我们来到一个似是而非的问题,即计算机真的能够把数字存储到文件中吗? 这样的文件真的能够以文本的方式进行打开吗? 最直观的答案是肯定的,但事实是残酷的否定。

因为你可以思考这样一种情况,对于数字 137 来说,我们如果在某个文本文件中看到它,我们当然可以用鼠标选中其中的 13 或者 7 中的任意一个数字,而逻辑上讲,137 是一个整体,如果计算机真的把它存储在了文件中,我们是无法单独获取其中的任意一个位值而不碰其他的值的。 (这涉及一个哲学问题,这就是,整体的部分的集合还是整体本身吗? 放心,这里只提问,而不做任何形式的讨论和解答)。

那么,计算机存储下来的数字是什么呢? 其实它们不再是数字,而是形如数字的字符串。 比如137 不是一个数字,而是'1' + '3' + '7' + '\0'这样的一个字符串,最后的\0代表文件数据中的字符串的终止标记。

综上所述,我们在文件中所面对的一列“数字”,在实质上是一列“字符”的组合。 而 W 系统使用utf-16编码对字符进行存储。所以我们当然需要使用该编码来解析它。

扩展编码集所导致的问题

然而,utf-16 编码方式的使用也是一个值得思考的问题。 事实上,就单纯的数字和拉丁字母而言,因为它们的数量有限,我们无须使用如此长的二进制编码。 但 W 系统提供了支持中文的语言包,由于中文字符数量远超拉丁字母,这就需要在编码端使用更长、更复杂的编码方案,以便扩充它的表达集合。 目前,最主流的兼容包括中文在内的多种语言字符的解决方案,是称为 Unicode 的字符集。 为了比 utf-8 更加完整地覆盖 Unicode 字符集(Unicode 的范围为0x0~0x10FFFF,可以用来表示大量的特异性字符),计算机系统必须做出广度和效率之间的妥协。

但这样做也导致了一个附加的问题,这就是用户必须也在广度和效率之间做出选择。 说人话,就是对于目前计算机系统中的任何一个文件,它都处于一个“薛定谔的猫”的状态,既有可能是使用效率较高的 utf-8 编码方案,也有可能是使用广度较高的 utf-16 或甚至更加复杂编码方案。 吊诡的是,单从文件中所包含的二进制序列来看,用户甚至无法单从内容判断出它到底属于哪种文件。 这是几乎一切编码问题的由来。

对此,我也无法提出任何良好方案。 只能提醒读者,在处理二进制文件的过程中,一定要事先确定它是如何进行编码的,这样可以有效避免程序的不确定行为,切记切记。

文件 = 内容 + 编码 (之四)

在《文件 = 内容 + 编码 (之三)》中,我们提到

目前,最主流的兼容包括中文在内的多种语言字符的解决方案,是称为 Unicode 的字符集。 ……为了比 utf-8 更加完整地覆盖 Unicode 字符集(Unicode 的范围为0x0~0x10FFFF,可以用来表示大量的特异性字符),计算机系统必须做出广度和效率之间的妥协。

​ 本部分是《文件 = 内容 + 编码》的最后部分,其目的是对之前的遗留问题进行说明,可以当作附录来使用。 但本部分内容也有其自然的逻辑,即同样的内容在计算机系统中,可以具有不同的表达或存储方式。


字符集 Unicode 的说明

当时只草草提到了 Unicode 字符集,并且武断地要求编码方案尽量多地覆盖其范围。 这不得不引起读者的好奇。 但为了不使单篇文章结构显得过于松散,无奈做此妥协,我们将在此进行补足。

与其简单地将 Unicode 称为是一种字符集,不如更加准确地把它想象一个针对各种字符的集合,目前可以提供 14 万字符的约定。

Unicode 13.0 adds 5,930 characters, for a total of 143,859 characters.

来自 http://www.unicode.org/versions/Unicode13.0.0/

具体文档可以参考其官方网站[2]

但与字面意思的字符集稍有出入的是,Unicode 并不是实实在在的字符编码表,而只是一种约定,这种约定的目的是为了让错综复杂的计算机系统能够共享一套字符集合,从而实现信息互通。 而在这种约定的指导下,各个计算机系统可以使用自己的编码方案实现这种约定。 实现时面临着 Unicode 字符集规模过大的难点,如果要对其中的全部字符进行编码,势必会导致编码效率过低。 而幸运的是,在特定使用条件下,往往只需要对其中的特定字符进行覆盖即可,如只覆盖中文字符、希腊字符、数学字符或 Emoji 字符等。 因此,才有了我们在前文的“更加完整地覆盖”之说。

这一点并不容易通过文字简述明白,我们不妨使用 Python 语言提供一个简单的例子,该例子提供了一个以特定 Unicode 字符为中心,对它“周围”的字符进行展示的功能。 通过该例子,我们将看到,在不同的编码方案下,同样字符“周围”的字符并不是同样的,这就从实例的角度说明了,特定的编码方案只是实现了 Unicode 字符集合的对应关系,而字符顺序则是在编码端的实现过程中,是具有一定自由度的。

下面展示了在三种编码中,对“工”字符及其周围的非空字符进行展示。

  • The Neighbor Table of "工" in "utf-8"
8 巀 巁 巂 巃 巄 巅 巆 巇 巈 巉 巊 巋 巌 巍 巎 巏
9 巐 巑 巒 巓 巔 巕 巖 巗 巘 巙 巚 巛 巜 川 州 巟
10 巠 巡 巢 巣 巤 工 左 巧 巨 巩 巪 巫 巬 巭 差 巯
11 巰 己 已 巳 巴 巵 巶 巷 巸 巹 巺 巻 巼 巽 巾 巿
  • The Neighbor Table of "工" in "utf-16"
- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 å ǥ ˥ ϥ ӥ ץ ۥ ߥ ࣥ ৥ ૥ ௥ ೥ ෥ ໥ ࿥
1 ქ ᇥ ዥ Ꮵ ᓥ ᗥ ᛥ ៥ ᣥ ᧥ ᫥ ᯥ ᳥ ᷥ ụ ῥ
2 ⃥ ⇥ ⋥ ⏥ ⓥ ◥ ⛥ ⟥ ⣥ ⧥ ⫥ ⯥ ⳥ ⷥ ⻥ ⿥
3 ュ ㇥ ㋥ ㏥ 㓥 㗥 㛥 㟥 㣥 㧥 㫥 㯥 㳥 㷥 㻥 㿥
4 䃥 䇥 䋥 䏥 䓥 䗥 䛥 䟥 䣥 䧥 䫥 䯥 䳥 ䷥ 以 俥
5 僥 凥 勥 句 哥 嗥 囥 埥 壥 姥 嫥 寥 峥 工 廥 忥
6 惥 懥 拥 揥 擥 日 曥 查 棥 槥 櫥 毥 泥 淥 滥 濥
7 烥 燥 狥 珥 瓥 痥 盥 知 磥 秥 童 篥 糥 緥 绥 翥
8 胥 臥 若 菥 蓥 藥 蛥 蟥 裥 觥 諥 该 賥 跥 軥 迥
9 郥 釥 鋥 鏥 铥 闥 雥 韥 飥 駥 髥 鯥 鳥 鷥 黥 鿥
10 ꃥ ꇥ ꋥ ꏥ ꓥ ꗥ ꛥ ꟥ ꣥ ꧥ ꫥ ꯥ 곥 귥 껥 꿥
11 냥 뇥 닥 돥 듥 뗥 뛥 럥 룥 맥 뫥 믥 볥 뷥 뻥 뿥
12 샥 쇥 싥 쏥 쓥 엥 웥 쟥 죥 짥 쫥 쯥 쳥 췥 컥 쿥
13 탥 퇥 틥 폥 퓥 헥 훥 ퟥ N N N N N N N N
14                
15          痢 﫥 ﯥ ﳥ   ﻥ ¥
  • The Neighbor Table of "工" in "gbk"
4 笯 笰 笲 笴 笵 笶 笷 笹 笻 笽 笿 筀 筁 筂 筃 筄
5 筆 筈 筊 筍 筎 筓 筕 筗 筙 筜 筞 筟 筡 筣 筤 筥
6 筦 筧 筨 筩 筪 筫 筬 筭 筯 筰 筳 筴 筶 筸 筺 筼
7 筽 筿 箁 箂 箃 箄 箆 箇 箈 箉 箊 箋 箌 箎 箏 N
8 箑 箒 箓 箖 箘 箙 箚 箛 箞 箟 箠 箣 箤 箥 箮 箯
9 箰 箲 箳 箵 箶 箷 箹 箺 箻 箼 箽 箾 箿 節 篂 篃
10 範 埂 耿 梗 工 攻 功 恭 龚 供 躬 公 宫 弓 巩 汞
11 拱 贡 共 钩 勾 沟 苟 狗 垢 构 购 够 辜 菇 咕 箍
12 估 沽 孤 姑 鼓 古 蛊 骨 谷 股 故 顾 固 雇 刮 瓜
13 剐 寡 挂 褂 乖 拐 怪 棺 关 官 冠 观 管 馆 罐 惯
14 灌 贯 光 广 逛 瑰 规 圭 硅 归 龟 闺 轨 鬼 诡 癸
15 桂 柜 跪 贵 刽 辊 滚 棍 锅 郭 国 果 裹 过 哈 N

Python 代码如下

# Defines
import pandas as pd
codings = ['utf-8''utf-16''gbk']
char = u'工'

# Utils
class UnicodeDetector(object):
    ''' Detector of Unicode '''

    def __init__(self, codings=codings):
        ''' The initializer of the Detector

        Args:
        - @self: Self instance;
        - @codings: The codings of interests.
        '''

        self.codings = codings
        pass

    def set_char(self, char):
        ''' Setup Example Char

        Args:
        - @self: Self instance;
        - @char: The Example Char to be detected.

        Returns:
        - @char: The Example Char;
        - @char_table: The Unicode of the Example Char in the coding of interests.
        '''

        char_table = dict()
        for c in codings:
            e = char.encode(c)
            print(c, '->', e)
            char_table[c] = e
        char_table

        self.char = char
        self.char_table = char_table

        return char, char_table

    def get_neighbors(self, coding=None):
        ''' Get neighbors of the Example Char

        Args:
        - @self: Self instance;
        - @coding: The coding of interest, default value is None.

        Returns:
        - @df: The neighbors DataFrame of the Example Char using the selected Coding.
        '''

        # Setup the default coding
        if not coding in self.codings:
            coding = self.codings[0]

        # Get the neighbors and save them into a 256 length array [lst]
        bs = [e for e in self.char_table[coding]]
        lst = []
        for j in range(256):
            bs[-1] = j
            c = None
            try:
                c = bytes(bs).decode(coding)
            except UnicodeDecodeError:
                pass
            lst.append(c)

        # Convert the [lst] into the DataFrame
        tmp = pd.DataFrame(lst)
        df = pd.DataFrame(tmp.to_numpy().reshape((1616)))

        self.coding = coding
        self.df = df

        return df

ud = UnicodeDetector()
ud.set_char(char)

遗留问题一的说明

在《文件 = 内容 + 编码 (之二)》中,我们提到

这里出现了 .jpg.png 格式之间莫名其妙的转换,……因此会不可避免地涉及我们在前面所提及的计算机图像对象存储和处理问题。

在撰写这部分内容时,还没有对 Unicode 字符集的实现进行介绍。 在进行了该介绍之后,我们便不难理解

同样的内容在计算机系统中,可以具有不同的表达或存储方式。

那么,同样的图像,采用不同的编码方法,如 .jpg.png 两种不同格式,就是同样顺理成章的事情。

如果要了解 .png.jpg 格式的细节,请稳步其官方网站

.png[3]

.jpg[4]

最后,希望通过这 4 篇文章的介绍,读者可以在一定程度上理解我写在前面的话,即

这就构成了一种两端的结构,一端是计算机要处理的物理世界,另一端是计算机的具体硬件,……而把这两个具体端联系起来的,我们可以理解成是抽象的编码过程。

参考资料

[1]

The Example jpg File: https://gss0.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D600%2C800/sign=72df17df52df8db1bc7b74623913f16c/d439b6003af33a87c7ef165dca5c10385243b5f2.jpg

[2]

Unicode Home Page: https://home.unicode.org/

[3]

PNG HomePage: https://www.w3.org/TR/PNG/

[4]

JPEG HomePage: https://jpeg.org/jpeg/

张春成

2021/06/09  阅读:103  主题:默认主题

作者介绍

张春成