开发者必知必会的ASCII规范与编程实现

444 阅读10分钟
原文链接: mp.weixin.qq.com

本文字数2163字

阅读时间:7分钟

本期特邀作者

德玛西亚之翼奎因,有两年Python开发经验,和丰富的爬虫经验,擅长反爬虫的绕过技巧。现为图灵签约作者,华为云官方认证云享专家,掘金社区优秀作者,著有《Python3反爬虫原理与绕过实战》。

导读

ASCII 全称为 「American Standard Code for Information Interchange」,即美国信息交换标准代码。这是一套基于拉丁文的计算机编码,也是目前计算机领域通用的信息交换标准。

在计算机中,信息在传输、存储和运算时使用的都是二进制数,例如a 在计算机中实际上是 01100001,或者说 0110 0001 在计算机的世界中表示的是a。

除此之外,你还可以用 01100001 来表示 b或者 k ,这取决于你选择的映射规则。

每个人都可以约定一套特别的映射规则,或者选择别人约定的映射规则。但这样就会导致大家「语言不通」,这场景就像一个英国人在跟一个澳大利亚人讨论美国的马戏。

因此,为了确保大家既能够互相通信又不会造成歧义,那么大家就必须使用相同的映射规则进行交流。美国相关标准组织就约定了一套映射规则,也就是我们现在使用的 ASCII。

计算机中有一些规则或约定会用到 ASCII 码,例如编程世界中常见到的 Base64 编码规则,因此ASCII 码作为信息交换标准代码,是非常重要的。

解读 ASCII 的规范

ASCII 的规范文档为 RFC20,即关于 ASCII 的所有约定都写在 RFC20 文档中。RFC20 的制定时间为 1969 年 10 月 16 日,整个文档分为 6 个部分:

  1. 编码范围

  2. 标准代码

  3. 字符表示和代码标识

  4. 字符与表示

  5. 定义

  6. 考虑因素说明

其中,我们需要关注的主要是第 2、3、4 等几个部分。

首先,我们来看看第 2 部分:标准代码,原文为「 Standard Code」。文档原文给出了一个由行和列组成的表,该表内容如下图所示:

第 3 部分给出了该表的查阅方法以及二进制和字符的映射关系。原文提到,使用 7 位二进制数来映射字符。

例如字符  的二进制表示由列号为 4 和行号为 11 的 7 个二进制数组成,即 100 1011。下图圈出了字符 K 的二进制表示:

要注意的是,二进制数的排序规则为从高到低,即 b7 ... b1。下图圈出了二进制数的高低序号:

接着,第 4 部分介绍了 ASCII 标准码中给出的字符和对应的表示。例如字符   DEL 表示的是 Delete ,即删除。

原文文档中给出的字符和对应的表示过于冗长难懂,有网友整理出了包含不同进制的对照表(部分截图,原表可前往查看):

由此,我们得知字符 a 的十进制表示是 97,而字符 L 的十进制表示是 76。

编程实现 ASCII 库

当我们需要在计算机世界中交换信息时,就有可能用到 ASCII。很多编程语言中并没有内置 ASCII 码与字符转换的函数,例如 Rust。但也有些编程语言内置了 ASCII 码与字符转换的函数,例如 Python。

1、参考 Python 已有的内置库

Python 内置的用于转换 ASCII 码和字符的函数为 ord() 和 chr(),这两个函数使用起来非常简单且方便。将字符转换为 ASCII 码时,只需要其传入 ord() 函数即可。假设需要将字符 a 转换为 ASCII 码,对应示例如下:

1>>>print(ord('a'))297

上方代码表示将字符 a 传入 ord() 函数,并打印结果。代码运行结果为 97,这与 RFC20 中约定的规则相符。相反的,将 ASCII 码转换为字符时,只需要其传入 chr() 函数即可。假设需要将 ASCII 码 98 转换为字符,对应示例如下:

1>>>print(chr(98))2b

上方代码表示将 ASCII 码 98 传入 chr() 函数,并打印结果。代码运行结果为 b,这与 RFC20 中约定的规则相符。

非常方便易用,不得不夸赞 Python 开发团队。但 Rust 开发团队并没有将 ASCII 相关的库放到 Rust 标准库中,如果想要在 Rust 代码中使用 ASCII 转换的功能,要么选择别人编写的库,要么自己用代码实现 RFC20 约定的映射规则。

2、编程实现原理和逻辑

有一定编程经验的朋友想必已经想到了,对于这种并不复杂,且内容不多的映射关系可以用键-值关系的数据类型或者容器来存储,在读取的时候速度就会很快,例如 Python 中的字典(dict)。Rust 语言中,比较合适,且有键-值关系的应当是 HashMap。

我觉得 Python 简洁的设计让开发者使用起来很流畅,所以 Rust 语言编写的 ASCII 库将参考 Python 简洁的风格,只设定 ord() 和 chr() 这两个方法。

从上面介绍的内容来看,ASCII 其实比较简单,只需要建立 1 个映射表,然后实现 ord() 和 chr() 方法。方法实现也比较简单,利用 HashMap 的 get() 方法把只取出来,返回给调用方即可。下图描述了 ASCII 库的组成和交互过程:

接下来,我们看看 Rust 语言来实现的符合 RFC20 规范的 ASCII 库。

3、编程实现(Rust 语言)

考虑到很多朋友都不熟悉 Rust 语言的语法,所以本次演示着重讲解代码逻辑。本次演示讲解是 crates.io (Rust 语言的包管理平台,类似 Python 中的 Pypi)上的 asciis 库,库的作者是我。

asciis 库的代码结构很简单:

一个结构体,一个 impl 和定义的  init()、 ord()、chr() 方法。这与我们常见的形参、类和方法和概念相似。

init() 方法返回的是存储 ASCII 标准码的 HashMap,ord() 方法和 chr() 方法负责从 HashMap 中取出对应的 Key 或者 Value。

以 ord() 方法为例,我们来看看具体的代码实现:

1pub fn ord(&self, value: &str) -> Option<i32>{2        let hmp = self.init();3        let res = hmp.get(value);4        match res{5            Some(n) => Some(*res.unwrap()),6            None => None7        }8    }

方法要求传入类型为 &str 的参数,输出类型为 Option<i32> 的结果。首先将 HashMap 存储的值赋值给变量 hmp,接着于用 get() 方法取出于传入参数 value 对应的值,并返回 Option<i32> 的结果。

至于为什么不返回数字类型而选择返回 Option<i32>,其实跟 Rust 的语法和设计风格有关。这是为了确保程序正常稳定运行,而不会在运行期间产生异常退出的情况。

Option 代表只有两种情况:「有值」或者「没有值」,Rust 将这两种情况叫给开发者去处理,而不是在「没有值」时返回 None 或其他,这样就可以避免运行过程突发异常,导致线程退出。

4、方法验证

在每一个 Rust 库发布到 crates.io 之前,都需要通过文档测试(当然,有些开发者无所谓)。我们正是通过文档测试来验证方法是否可用,或者会突发异常等情况。asciis 库的测试文档如下:

 1//! ASCII base on RFC20. 2//! 3//! 基于 ASCII 规范文档 RFC20 编写的库 4//! 5//! This crate is very simple, just ord() and chr() 6//! 7//! 这个库非常简单易用,因为只有 ord() 和 chr() 这两个方法 8//! 9//!10//! **ord**11//!12//! Given a string representing one Unicode character, return an integer representing the Unicode code point of that character.13//!14//! For example, ord('a') returns the integer Some(97) and ord("s") returns Some(115).15//!16//! This is the inverse of chr().17//!18//!19//! **chr**20//!21//! Return the string representing a character whose Unicode code point is the integer i.22//!23//! For example, chr(97) returns the Some(), while chr(115) returns the String Some("s").24//!25//! This is the inverse of ord().26//!27//! # ord()28//! &str to Some(i32), "s" -> Some(115)29//!30//! Example:31//!32//!     use asciis::asc::Asciis;33//!     let asc = Asciis{};34//!     let r = asc.ord("s");35//!     assert_eq!(r, Some(115));36//!37//! # chr()38//! i32 to Some(String), 97 -> Some("a")39//!40//! Example:41//!42//!     use asciis::asc::Asciis;43//!     let asc = Asciis{};44//!     let v = asc.chr(97);45//!     assert_eq!(v, Some(String::from("a")));

Rust 库的文档测试中列出的所有内容最终会成为这个库的说明文档,所以这里为了文档简洁一些,省去了很多的 assert_eq(),只保留了正确用法示例。

当开发者在控制台运行 cargo test 命令后,就会启动 Rust 自带的文档测试功能,在测试结束后输出如下结果:

1running 2 tests2test src/lib.rs -  (line 32) ... ok3test src/lib.rs -  (line 43) ... ok45test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

这代表共执行了 2 个测试,并且两个测试都通过了。

asciis 库的完整代码可在我的 GitHub 仓库查看,本文不再赘述。

至此,我们已经完成了 ASCII 库的编码实现。

总结

通过本篇文章的学习,相信你对 ASCII 有了一定的了解,现在我们来回顾一下本篇内容,以巩固所学。

首先,在开篇处我们了解到 ASCII 的具体含义和产生的原因。然后解读了 ASCII 规范文档 RFC20,了解到 ASCII 的标准码组成和具体含义。接着参考简约 Python 语言内置的的 ASCII 转换方法:ord() 和 chr() 。

最后,在掌握编程实现原理并具有清晰的逻辑情况下阅读了 asciis 库的源码和文档测试中的示例代码。

阅读完本篇文章后,你是否对 RFC 和编码规范有了新的认识呢?

欢迎在评论区留下你的看法哦!

参考资料:

[1] https://tools.ietf.org/html/rfc20

[2] http://ascii.911cha.com/

[3] https://crates.io/crates/asciis

[4] https://github.com/asyncins

也许你还想看

(▼点击文章标题或封面查看)

iOS性能优化之计算多行Label高度的新方法

2019-01-03

Flutter移动端实战手册 

2019-06-20

不了解GIF的加载原理?看我就够了! 

2019-02-28

加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛

     

     

      您对本文有什么疑问吗?

      点我写留言

  ▼▼▼