150 |
151 | (0.110 0100 1000 1110 0110 1000 * 2^23)/(2^23)
152 |
153 | =(0x49E48E68 & 0x007FFFFF)/(2^23)=(0x648E68)/(2^23)
154 |
155 | = 0.78559589385986328125
156 |
157 |
158 |
159 |
160 | 即 X = 0.78559589385986328125。这样,该浮点数的十进制表示为:
161 |
162 |
163 |
164 | = (- 1)^S \* (1 + X) * 2(N - 127)
165 |
166 | = (- 1)^0 \* (1 + 0.78559589385986328125) * 2^(147 - 127)
167 |
168 | = 1872333
169 |
170 |
171 |
172 |
173 | 在浮点数的应用中,我们需要注意,我们常见的十进制表达的有限不循环小数不能用二进制浮点数做精确表达,因为我们要将这些实数使用 2 的阶数而不是 10 的阶数表示。因此存在误差,在经过一定的浮点数运算之后,这种误差会扩大。比如 0.1 和 0.01[^2],0.01 是 0.1 的平方。在单精度浮点数中,表示 0.1 的最接近的数是:
174 |
175 |
184 |
185 | 0.10000000298023226097399174250313080847263336181640625
186 |
187 |
188 |
189 | 但最接近 0.01 的二进制浮点数却是:
190 |
191 |
240 |
241 | double cos(double x);
242 | float cosf(float x);
243 | long double cosl(long double x);
244 | ```
245 |
246 | 因此,我们在使用符合 C99 规范的数学库时,需要根据不同的浮点数类型来调用不同的函数接口。
247 |
248 | 另外,目前某些平台上的格式化输入和输出函数,如 `printf/scanf` 等,尚不能提供对 `long double` 浮点数的正确支持,因此,读者在使用 `long double` 时应该注意到这一点。
249 |
250 | ## 3.3 定点数
251 |
252 | ### 3.3.1 定点数的概念
253 |
254 | 在没有浮点协处理器的计算机系统上,如果要进行大量的浮点数运算,其性能会很低。因此,人们想到了另外一种办法来满足高性能实数计算的需求,也就是使用定点数。
255 |
256 | 定点数其实很好理解。比如在会计处理中,我们可以将表示现金的数字全部乘以 100,然后再进行计算。这时,原本需要浮点数的运算就变成了整数运算,将结果再除以 100,余数就是角和分。
257 |
258 | 因此,定点数实质上是整数。我们始终用整数中给定不变的位数来表示一个实数的整数部分,然后用其余的位来表示实数的小数部分(而浮点数的整数部分和小数部分所占的位数会根据实数的值发生变化)。这样,如果用定点数来表示实数,则四则运算可用整数的四则运算来处理,其他的非线性运算(比如平方根、立方根、三角运算等)则可以用查表法得到。
259 |
260 | 这种基于定点数的运算,存在如下优点和缺点:
261 |
262 | - 运算速度非常快。因为大多数的运算实质上是整数运算,或者简单的线性查表运算,因此,定点数的运算速度和整数运算相当。
263 | - 可表示的实数范围有限,且精度较低。比如,如果我们的定点数小数部分只有 4 位,则精度只能达到小数点后两位。
264 |
265 | 由于定点数运算的范围和精度有限,因此,定点数通常用于运算结果被限定在某个区间中的情形。比如,在图形学中,表示一个像素值的 RGB 分量取值区间是有限的([0,255]),此时,如果采用定点数进行像素的混合运算,则其结果是可接受的。
266 |
267 | ### 3.3.2 定点数的 C 语言实现
268 |
269 | 在 32 位计算机上,我们可以使用带符号的 32 位整数来表示一个定点数,取值范围从 -32767.0 到 32767.0;一个定点数用高 16 位表示符号及实数的整数部分,用低16位表示小数部分。类似地,在 64 位计算机上,我们可以使用 64 位整数来表示一个定点数,其取值范围和精度将大大超过 32 位系统。
270 |
271 | 使用 C 语言,浮点数和定点数之间相互转换的函数可以如下实现:
272 |
273 | ```c
274 | typedef int fixed;
275 |
276 | static inline fixed ftofix (double x)
277 | {
278 | if (x > 32767.0) {
279 | errno = ERANGE;
280 | return 0x7FFFFFFF;
281 | }
282 |
283 | if (x < -32767.0) {
284 | errno = ERANGE;
285 | return -0x7FFFFFFF;
286 | }
287 |
288 | return (long)(x * 65536.0 + (x < 0 ? -0.5 : 0.5));
289 | }
290 |
291 | static inline double fixtof (fixed x)
292 | {
293 | return (double)x / 65536.0;
294 | }
295 | ```
296 |
297 | 注意,上述函数使用 C 语言的全局 errno 来表示可能出现的超出运算范围等情形。因此,在调用这些函数之后,应该检查 errno 是否有被设置,并在出现错误的情形下做相应处理。
298 |
299 | 相比浮点数,整数和定点数之间的转换函数更为简单:
300 |
301 | ```c
302 | static inline fixed itofix (int x)
303 | {
304 | return x << 16;
305 | }
306 |
307 | static inline int fixtoi (fixed x)
308 | {
309 | return (x >> 16) + ((x & 0x8000) >> 15);
310 | }
311 | ```
312 |
313 | 这样,定点数的加减运算就变成了整数的加减运算;如下是定点数加法运算:
314 |
315 | ```c
316 | static inline fixed fixadd (fixed x, fixed y)
317 | {
318 | fixed result = x + y;
319 |
320 | if (result >= 0) {
321 | if ((x < 0) && (y < 0)) {
322 | errno = ERANGE;
323 | return -0x7FFFFFFF;
324 | }
325 | else
326 | return result;
327 | }
328 | else {
329 | if ((x > 0) && (y > 0)) {
330 | errno = ERANGE;
331 | return 0x7FFFFFFF;
332 | }
333 | else
334 | return result;
335 | }
336 | }
337 | ```
338 |
339 | 定点数的乘除法运算要相对麻烦一点,要仔细处理符号位以及小数部分相乘的进位问题,如下所示:
340 |
341 | ```c
342 | fixed fixmul (fixed x, fixed y)
343 | {
344 | int s1 = 1, s2 = 1;
345 | long result;
346 | unsigned long op1_hi;
347 | unsigned long op1_lo;
348 | unsigned long op2_hi;
349 | unsigned long op2_lo;
350 | unsigned long cross_prod;
351 | unsigned long prod_hi;
352 | unsigned long prod_lo;
353 |
354 | if (x < 0) {
355 | s1 = -1;
356 | x = -x;
357 | }
358 |
359 | if (y < 0) {
360 | s2 = -1;
361 | y = -y;
362 | }
363 |
364 | op1_hi = (x >> 16) & 0xffff;
365 | op1_lo = x & 0xffff;
366 | op2_hi = (y >> 16) & 0xffff;
367 | op2_lo = y & 0xffff;
368 | cross_prod = op1_lo * op2_hi + op1_hi * op2_lo;
369 | prod_hi = op1_hi * op2_hi;
370 | if(prod_hi > 0x7FFF){
371 | errno = ERANGE;
372 | return 0x7FFFFFFF;
373 | }
374 | prod_lo = ((op1_lo * op2_lo) >>16) + cross_prod;
375 |
376 | prod_hi = (prod_hi << 16) + prod_lo;
377 | if(prod_hi > 0x7FFFFFFF){
378 | errno = ERANGE;
379 | return 0x7FFFFFFF;
380 | }
381 | result = s1 * s2 * prod_hi;
382 |
383 | return (fixed)result;
384 | }
385 | ```
386 |
387 | 对求平方根、三角运算等非线性运算则采用查表法实现。以平方根为例,该实现首先构造了一个 0~256 值的平方根线性表:
388 |
389 | ```c
390 | static unsigned short _sqrt_tabl[256] =
391 | {
392 | 0x2D4, 0x103F, 0x16CD, 0x1BDB, 0x201F, 0x23E3, 0x274B, 0x2A6D,
393 | 0x2D57, 0x3015, 0x32AC, 0x3524, 0x377F, 0x39C2, 0x3BEE, 0x3E08
394 | ...
395 | 0xF7E3, 0xF867, 0xF8EA, 0xF96E, 0xF9F1, 0xFA74, 0xFAF7, 0xFB79,
396 | 0xFBFB, 0xFC7D, 0xFCFF, 0xFD80, 0xFE02, 0xFE82, 0xFF03, 0xFF83
397 | };
398 | ```
399 |
400 | 然后利用如下数学公式推导,我们可将任意实数的平方根转化为依赖于上述线性表的四则运算:
401 |
402 | ```c
403 | 因为:sqrt (x) = sqrt (x/d) * sqrt(d)
404 | 设:d = 2^(2n)
405 | 则:sqrt (x) = sqrt (x / 2^(2n)) * 2^n
406 | ```
407 |
408 | 在最后的等式中,只要将等式右边的 sqrt 运算限制到 0~255 之间,我们就可以利用在上面给出的 0 ~ 255平方根表中查表获得对应的值,再乘以 2^n 即可得到 sqrt (x) 的值。这样,找到 n 就可以仅仅通过查表和乘法运算计算出 sqrt (x)。而从十六进制的运算规律可知,x / 2^(2n) 恰好相当于将 x 右移 2n 位之后的值。因此,我们只需要知道将 x 右移多少位之后小于 256,即可得到 2n 的值。有了上述推导,定点数的 sqrt 函数实现方式如下:
409 |
410 | ```c
411 | fixed fsqrt(fixed x)
412 | {
413 | int i, dx;
414 | int cx = 0; /* if no bit set: default %cl = 2n = 0 */
415 |
416 | /* 判断负值... */
417 | if (x <= 0) {
418 | if (x < 0)
419 | errno = EDOM;
420 | return 0;
421 | }
422 |
423 | /* bit-scan is done on dx */
424 | dx = x >> 6;
425 | for (i = 0; i < 32; i++) {
426 | if (dx << i & 0x80000000) {
427 | cx = 32 - i;
428 | break;
429 | }
430 | }
431 |
432 | cx &= 0xFE; /* 确保结果为偶数 --> %cl = 2n */
433 | x >>= cx; /* 右移 x 使其取值范围在 0..255 之间 */
434 |
435 | x = _sqrt_tabl [x]; /* 查表... */
436 |
437 | cx >>= 1; /* %cl = n */
438 | x <<= cx; /* `sqrt(x/2^(2n))' 乘以`2^n' */
439 |
440 | return x >> 4; /* 调整结果 */
441 | }
442 | ```
443 |
444 | ## 3.4 任意精度运算
445 |
446 | 由于浮点数和定点数在实数表达上存在的缺点(浮点数在表示大数的时候会损失小数部分的精度,而定点数的表达范围有限且小数部分的精度也是有限的),仍无法满足某些领域的运算要求。因此,人们又发明了用于任意精度运算的程序或函数库。当然,任意精度运算本质上仍然是有限精度运算,只是其可以表达的精度范围随着计算机可以使用的内存可以无限制增长。
447 |
448 | 因为任意精度运算的应用场合较窄,因此尚未形成业界标准。现今,主要的任意精度运算由 GNU 项目的两个开源(自由)软件实现:
449 |
450 | bc(bench calculator),是一种运行在 UNIX 类操作系统上的任意精度计算器程序,可支持2,147,483,647 位小数点位数。该程序支持类似 C 语言的语法,可编程表达计算公式。
451 |
452 | GMP(GNU Multiple Precision Arithmetic Library,GNU 多精度运算库,简称 GMP),是使用 C 语言开发的多精度运算函数库,可支持整数、有理数以及浮点数,其精度仅受限于可用内存。GMP 可用于密码应用和研究、互联网安全应用、代数系统以及计算代数研究等等。
453 |
454 | [^1]: 【维基百科】在1980年,英特尔公司就推出了单片的8087浮点数协处理器,其浮点数表示法及定义的运算具有足够的合理性、先进性,被IEEE采用作为浮点数的标准,于1985年发布。而在此之前,浮点数的二进制表示混乱而缺乏统一标准。
455 |
456 | [^2]: 这个例子取自【维基百科】。
457 |
458 | [^3]: 即 IEEE 754。
459 |
460 |
--------------------------------------------------------------------------------
/textbook/part-1-chapter-4.md:
--------------------------------------------------------------------------------
1 | # 第 4 章 文字:字符集、编码
2 |
3 | 计算机程序的运算有了结果就要输出给人看,且应以便于人类认知的方式来展示结果。最好的方式就是使用人类使用的文字来输出结果了。然而,人类文明的发展丰富多样,语言文字变化多端。对计算机来讲,正确表达不同的文字信息是需要首先解决的问题。本章讲述文字这类信息的计算机表述方法。
4 |
5 | ## 4.1 文字的编码需求
6 |
7 | 为了形象地描述使用计算机表述文字的历史演进过程,我们在这一小节引入两个主人公小明和李教授。小明在一所大学的电子工程实验室做自己的本科论文,李教授是他的导师。
8 |
9 | 小明这会儿正在使用纸带编写一段可连续打印圆周率的程序,这段程序源源不断地在纸带上打眼来输出二进制的圆周率。小明马上发现,二进制的数字得转换成十进制看呀,否则李教授怎么知道我这程序的输出是正确的?起码就现在大家都知道的圆周率小数点后十几位:“山巅一寺一壶酒(3.14159),尔乐苦煞吾(26535),把酒吃(897),酒杀尔(932),杀不死(384),乐尔乐(626)”,这程序要能正确输出吧?
10 |
11 | 为了实现这个目标,小明编写了一段程序,把圆周率的二进制表达变成了十进制的表达,每个字节表达一个十进制数,哦,还有小数点。反正就十一个字符,小明就用0x00 表示十进制 0,0x01 表示 1,0x02 表示 2,依此类推,然后用 0x10 表示小数点。这下,程序输出的圆周率就变成了:
12 |
13 | ```
14 | 0x03 0x10 0x01 0x04 …
15 | ```
16 |
17 | 但使用计算机,怎么把这个结果展示出来呢?要知道,那时的计算机可没有显示屏呀。小明很聪明,懂数字电路,他很快又搭了一个可以发光的数码管电路,这个电路根据 0x00 到 0x10 这几个数字,可以使用八段数码管显示十进制的 0 ~ 9 数字还有小数点。当小明的程序运行起来的时候,这个数码管从 3 开始,每隔一秒显示圆周率十进制表示的下个数字[^1]以及可能的小数点。
18 |
19 | 李教授看到小明的工作成果非常开心,李教授大大表扬了小明,称小明的这个发明是划时代的,这让计算机有了一种能力,可以用人类能够理解的方式来表达自己。嗯,的确有点意思。最终,小明凭这个作品拿到了本科学位并开始在李教授的项目组攻读研究生。
20 |
21 | 小明上研究生的头一天,李教授给了他一个题目,说现在有录音机了,可以录制和播放人的声音。你不是读过香农的信息论吗?看看能不能用磁带把你的计算好的圆周率录下来,将来也好给别人看呀。
22 |
23 | 小明很快将这个功能实现了,原理很简单,就是把计算好的圆周率结果录制到磁带上,用二进制方式,每个磁粉的极性表示二进制的 0 或者 1。小明甚至把自己的名字还有李教授的名字所使用的拼音字母(包括空格)也编了码,比如 0xA1 表示 a,0xA2 表示 b,依此类推。一起放到了录制好的磁带上,“THE PI IS CALCULATED BY A PROGRAM WRITTEN BY XIAO MING AND DIRECTED BY PROFESSOR LI.”
24 |
25 | 小明花了一年时间就把这项工作做完了,很快申请论文的答辩。作为研究生科研成果一部分,这磁带被寄给了小明研究生论文的评审教授王教授那里。王教授很欣赏这项工作,认为这项成果不仅仅可以用来记录程序的输出,甚至连同程序自身在内,亦可用磁带录制。不过王教授对录制的内容中没有出现自己的名字而感到不悦。于是,王教授告诉小明,你这项工作成果还不错,但尚不足以申请硕士学位论文,因为没有理论支撑啊!你需要将你的编码方法系统化,而且利于计算机处理。
26 |
27 | 小明很是郁闷,本来可以提前毕业去工作挣钱,那时的英特尔公司刚刚成立正在招人哩。要是丧失了这个机会,就没法成为创始员工了。李教授劝他,计算机行业刚刚开始,以后机会多得是,你好好把自己的成果拔拔高,将来一旦成名,那机会就自己找上门来了。
28 |
29 | 小明开始仔细琢磨王教授的评语,琢磨好几天没有头绪。有一日,小明跑到学校的图书馆,看到有人正在使用打字机打印求职信。小明一下子豁然开朗,我为什么不能将打字机和计算机连起来?打字机作为计算机的输入,把打字机上的每个按键编个号,程序记录这些编号,这样就可以直接用打字机来输入程序,然后执行,再将程序执行结果输出到磁带上。
30 |
31 | 于是,小明快速设计了计算机使用的键盘,这个键盘可以将用户的每个按键进行编码并传输到计算机中进行处理。在之前的基础上,小明扩充了若干控制字符,比如回车啊,换行之类的,而且还区分了英文的大小写字母,添加了常用的标点符号。
32 |
33 | 简单来说,小明的这个编码系统使用七个比特位来表示一个字符,取值范围为 0x00 到 0x7F,可以表示回车、换行、缩进(TAB)等控制字符,也可以表示 26 个拉丁字母(区分大小写),还包括十进制阿拉伯数字以及常用的标点符号。
34 |
35 | 为什么使用七位呢?小明多次实验发现,将键盘通过一条线连接到计算机上时,因为这根线传输距离长,而且没有屏蔽层保护,传输的比特位经常会出现错误的情形。为此,小明使用了我们在第二章中所说的“奇偶校验”方法,将一个字节的最高位作为偶校验码传输过去。计算机收到一个字节后,如果不符合偶校验规则,则认为传输失败,键盘灯就会亮一下,提醒敲键盘的人再次输入。这样,就只能使用字节的低七位来编码了。但同时,小明的硕士论文一下子就高大上了!有工程方法还有理论依据,而且小明还将自己的硕士论文全部使用打字机录入然后保存在了磁带上。虽然王教授的名字最终只是出现在了论文的致谢部分,但王教授也没啥可说的,毕竟只是硕士论文嘛。最终,小明欢欢喜喜拿到了硕士学位,走向社会,大踏步迈向了自己未来的高富帅生活。
36 |
37 | 尽管这个故事是虚构的,不反映任何真实历史。但小明在这里使用的编码方法,就是我们所熟知的 ASCII[^2] 的雏形。每个工程师和科学家都可以自己定义自己的编码方式,但为了交换数据方便,对文字的编码最终都会变成一项国家标准或者国际标准。ASCII 可以说是一切文字编码标准的源头,且深受其影响。
38 |
39 | ## 4.2 字符集及其编码
40 |
41 | 字符集(character set 或 charset),是为了表示某种语言文字而定义的字符集合;编码则是为了在计算机中表示某个字符集中的字符而设定的编码规则,它通常以固定的顺序排列字符,每个字符对应一个特定的字节或者字节序列,并以此作为记录、存储、传递、交换的统一内部特征。一般而言,字符集由某个国家或者国际标准化组织[^3]作为强制或推荐标准颁布。在字符集的定义当中,通常使用码值的概念,就是字符集当中各个字符的编号。比如在我们国家于 1980 年颁布的 GB2312-1980[^4] 字符集标准中,所定义的汉字/符号被分为87个区,每个区包含 94 个汉字/符号。这样,我们可以用特定汉字/符号在 GB2312 字符集中的哪个区、哪个位来指代它,这就是汉字“区位码”的概念。比如,“啊”字是 GB2312 中的第一个汉字,是第 16 区的第一个字符,它的区位码就表达为 1601,或者十六进制的 0x1001。我们将类似区位码的这种字符编号称为字符的“码值”。
42 |
43 | 但是,在计算机中存储或者表示特定字符集中的字符时,我们需要考虑很多其他因素,最重要的就是兼容其它已有及基础字符集的问题,以及易于表达和处理的问题。这样,特定字符集中的字符,会以某个不同于字符码值的方式表示,这就是编码的概念。比如上面的 GB2312 字符集中的字符,大部分情况下使用后面提到的 EUC-CN 编码方法,这种方法用两个字节来表示一个汉字。其中,第一个字节的取值范围为 0xA1-0xF7(区位码中的区号加上0xA0),第二个字节的取值范围为 0xA1-0xFE(区位码中的位号加上 0xA0)。例如,“啊”字的 EUC-CN 编码就是 0xB0A1。需要注意的是,某些字符集本身定义了编码的方式,这种情况下,码值和编码是一样的。
44 |
45 | 计算机领域中首次出现的字符集及其编码标准是 ASCII,即美国信息交换标准代码。该字符集编码最初由美国标准协会[^5]于 1968 年制定,并在之后成为国际标准化组织制定和颁布的 ISO 646 标准。ASCII 一共定义了 128 个字符,其中包含英语使用的 26 个小写拉丁字母和 26 个大写拉丁字母、阿拉伯数字、标点符号以及若干控制字符。在十六进制形式下,ASCII 使用 7 位字节来表示一个字符,取值范围从 0x00 到 0x7F。表 4-1 给出了 ASCII 字符的十六进制编码分布情况。
46 |
47 | 表 4-1 ASCII 字符的十六进制编码分布
48 |
49 | | 取值或范围 | 代表字符 |
50 | |:-----------|:--------------|
51 | | 0x00 | 空(NULL)字符,C 语言表达为 \0。 |
52 | | 0x01~0x1F | 控制字符,其中 0x07~0x0D 为常用控制字符,分别表示蜂鸣(\a)、回退(\b)、水平制表符(\t)、新行(\n)、垂直制表符(\v)、进纸(\f)、回车(\r)等。 |
53 | | 0x20 | 空格。 |
54 | | 0x21~0x2F | 感叹号等标点符号。 |
55 | | 0x30~0x39 | 阿拉伯数字0~9。 |
56 | | 0x3A~0x40 | 冒号等标点符号。 |
57 | | 0x41~0x5A | 大写拉丁字母A~Z。 |
58 | | 0x5B~0x60 | 左右中括号等标点符号。 |
59 | | 0x61~0x7A | 小写拉丁字母 a~z。 |
60 | | 0x7B~0x7E | 左右花括号等标点符号。 |
61 | | 0x7F | 删除符(DEL)。 |
62 |
63 |
64 | 注意表 4-1 中的 ASCII 字符的编码取值范围,我们很容易发现,小写拉丁字母和大写拉丁字母并不是连续排列的。也就是说,这 128 个字符的编码方式存在一些特殊的考量,如:
65 |
66 | - 给定一个大写的拉丁字母,通过对其编码做“或”0x20 的运算,得到的就是该字母的小写字符。比如对字母A,其编码值为 0x41,0x41 | 0x20 后的值为 0x61,得到的就是小写 a 的 ASCII 编码,反之亦然。这种设计,使得早期的机械键盘或者电子键盘可更加容易地实现按键的大小写切换;我们可以按住键盘上的换挡(Shift)键或者打开大写锁定(CapsLck)键来输入大写字母。当然,借助此编码特性,也方便我们在程序中实现快速的大小写字母转换。
67 | - 类似地,我们在 PC 键盘上看到的可通过换挡键切换输入字符的按键,在编码上也只有单个位的区别,比如 ! 和 1,在 PC 键盘上用单个按键输入,其编码分别为 0x21 和 0x31。
68 | - 除了字母、数字和标点符号之外, ASCII 中含包含了大量的控制字符。这些控制字符通常不是可打印字符,其主要用途有:1)用于在屏幕上显示或者打印时控制字符的输出位置(如换行、回车、制表符、送纸等);2)用于交互输入场景(如蜂鸣、回退、删除);3)用于串口通讯。第三类用途在当今已经非常少见了,只有在接近硬件的嵌入式系统开发才会遇到。
69 |
70 | 显然,一个字符集及其编码的定义是并不是随意拼凑、排列的结果,而需要一些技巧。当然,字符集及其编码的定义也会受限于当时的计算机处理能力以及制定者在当时可以预见的未来。
71 |
72 | 在计算机的应用范围扩大到全球各个地区的时候,仅仅使用 ASCII 无法满足非英语国家的需求。首先是经济较为发达的拉丁语系国家,ASCII 并没有包含法语、德语等拉丁语系以及西里尔语、阿拉伯语、希腊语、希伯来语使用的字母。为此,国际组织定义了 ISO 8859 系列字符集,作为 ASCII 字符集的扩展:
73 |
74 | - ISO 8859-1:西方欧洲语言(Latin-1)
75 | - ISO 8859-2:中部和东部欧洲语言(Latin-2)
76 | - ISO 8859-3:东南欧洲语言和其他语言(Latin-3)
77 | - ISO 8859-4:斯堪的纳维亚语/波罗的语(Latin-4)
78 | - ISO 8859-5:拉丁/西里尔语
79 | - ISO 8859-6:拉丁/阿拉伯语
80 | - ISO 8859-7:拉丁/希腊语
81 | - ISO 8859-8:拉丁/希伯来语
82 | - ISO 8859-9:Latin-1针对土耳其语的修订(Latin-5)
83 | - ISO 8859-10:拉普兰/北欧/爱斯基摩人语(Latin-6)
84 | - ISO 8859-11:拉丁/泰语
85 | - ISO 8859-13:波罗的海沿岸语言(Latin-7)
86 | - ISO 8859-14:凯尔特语(Latin-8)
87 | - ISO 8859-15:西部欧洲语言(Latin-9)
88 | - ISO 8859-16:罗马尼亚语(Latin-10)
89 |
90 | ISO 8859 系列标准所定义的字符集及其编码有如下特点:
91 |
92 | - 均为 ASCII 编码的扩展,使用八位单字节定义每个字符,除 ASCII 定义的字符之外,其余字符使用 0xA0\~0xFF 区间范围定义字符。也就是说,ISO 8859 系列字符集向前兼容 ASCII 字符集,且保留 0x80~0x9F 这个区间未被使用。
93 | - ISO 8859 系列字符集之间是互相不兼容的,但标准的制定者也为这些字符集中的公共字符定义了相同的编码值。
94 |
95 | 在 ISO 8859 系列字符集中,最为重要的字符集是 ISO 8859-1 字符集。使用该字符集,可支持大部分拉丁语系语言,包括南非荷兰语、巴斯克语、加泰罗尼亚语、丹麦语、荷兰语、英语、法罗语、芬兰语、法语、加利西亚语、德语、冰岛语、爱尔兰语、意大利语、挪威语、葡萄牙语、苏格兰、西班牙语和瑞典语。下个小节中提到的 Unicode 字符集之前 256 个字符来自于 ISO 8859-1 字符集,且 Unicode 的 UTF-8 编码兼容 ASCII 字符集。
96 |
97 | 对于上面提到的拼音文字(以拉丁语系为代表),基本上使用8位的单字节编码即可表达对应语言的所有文字。但对汉字为代表的象形文字,却无法使用单字节的编码形式来定义所有的文字,毕竟《康熙字典》收录的汉字就有大概四万多个。为了在计算机中方便处理以汉字为代表的语言文字,中国大陆、中国台湾、日本等国家在 ASCII 的基础上各自制定了自己的字符集及其编码标准。
98 |
99 | 上面提到的 GB2312 标准就是中国定义的简体中文字符集标准,其中含有 682 个符号、6,763 个汉字;它共分 87 个区,每个区含 94 个字符。类似的还有日本的JISX0201、JISX0208 字符集,以及中国台湾、香港等地区广泛使用的 BIG5 繁体中文字符集等。
100 |
101 | 一个字符集可以有不同的编码形式。拿 GB2312 字符集来讲,通常我们使用的是上面所讲的 EUC-CN 编码。还有一种常见的 GB2312 编码形式是 HZ 编码,它去掉了 EUC 编码的最高位,使得汉字可以用 ASCII 码中的字符来表示,比如 EUC 编码中的汉字“啊”编码为“0xB1A1”,而 HZ 编码则为“~{1!~}”。这种编码方式主要应用于早期互联网电子邮件系统,那时的电子邮件系统不能正确处理 ASCII 字符集之外的字符,所以人们使用 HZ 编码来传输汉字。当然,现在这种编码方式基本上不再使用了。
102 |
103 | 在上个世纪 90 年代,随着个人计算机在国内的普及,GB2312 字符集定义的六千多个汉字远远不能满足互联网、出版界以及教育界的需求。最为尴尬的就是朱镕基总理的“镕”字没有被收录到 GB2312 字符集当中,导致在互联网刚刚进入中国时(1996年左右)的中文网页上,无法正确显示“镕”字。这种标准缺位的问题存在了很长一段时间。为了解决现实问题,微软在其 Windows 95 操作系统中自行扩展了 GB2312 字符集,纳入了 BIG5 字符集定义的繁体汉字以及其他一些常用汉字,称为 GBK。需要注意的是,该字符集并不是中国国家标准。另一方面,从上个世纪 90 年代开始,美国的主要 IT 巨头公司开始联合制定 Unicode 字符集,我国政府则自行制定了 GB18030 字符集,并要求所有在国内销售的操作系统必须支持 GB18030 字符集。从字符集定义的字符范围来看,GB18030 和 Unicode 类似,可用来定义海量的字符,且 GB18030 的编码方式向前兼容 GB2312 和 GBK。但时至今日,Unicode 及其 UTF-8 编码最终成为事实上的工业标准。这一字符集标准的争夺战及其发展历史,值得回味和反思。
104 |
105 | 随着各个国家、地区字符集标准的出台和升级,兼容性问题的引入是不可避免的。比如,一个采用 GB2312 EUC-CN 编码的文本文件无法在采用 BIG5 编码的系统上正常显示,若要正常显示和处理,则需要额外进行字符集及编码的转换。为此,一些国际组织开始致力于全球统一字符集标准的开发,也就是我们熟知的 Unicode/ISO 10646。
106 |
107 | ## 4.3 Unicode 字符集及 UTF-8 编码
108 |
109 | 国际标准组织早在 1984 年 4 月就成立了 ISO/IEC JTC1/SC2/WG2 工作组,开始针对各国文字、符号定义统一的字符集及编码标准,即 ISO 10646 统计字符集(UCS[^6])标准。于此同时,位于美国加州的主要 IT 巨头公司如微软、苹果、IBM、惠普等则成立 Unicode 组织来定义自己的 Unicode 字符集。
110 |
111 | 1991 年 Unicode 组织变身为 Unicode 财团,并于当年 10 月与 ISO/IEC JTC1/SC2/WG2 达成协议,采用同一编码字符集,并密切协调各自标准的进一步扩展,同时公布了 Unicode 1.0。1997 年 9 月发布的 Unicode 2.0 是一个更为成熟的 Unicode 版本,并在各操作系统中广泛使用。自 Unicode 2.0 开始保持了向后兼容,即新的版本仅仅增加字符,原有字符不会被删除或更名。Unicode至今仍在不断增修,每个新版本都会加入更多新的字符。目前最新的版本为 2014 年 6 月 16 日公布的 7.0.0,已收入超过十万个字符(第十万个字符在 2005 年获采纳)。Unicode 8.0.0 将于 2015 年 6 月发布(已发布草案)。由于ISO 10646 统一字符集标准的扩展速度要比 Unicode 快,因此,人们通常认为前者是后者的超集。
112 |
113 | 当前主流的操作系统(如 Linux、Windows 等)所支持的 Unicode 版本为 Unicode 3.0。Unicode 3.0 版本包含字母及符号 10,236 个,CJK(中日韩)汉字 27,786 个,韩文拼音 11,172 个,总的已分配字符为 49,194 个;另有保留私用码位 6,400 个,替代码位 2,048 个,控制字符 64 个、非字符 2 个;总分配码位 57,709 个,未分配码位 7,827,共计 65,536 个码位。
114 |
115 | Unicode 3.0 总共定义了 65,536 个码位(除去0xFFFE 和 0xFFFF 两个非字符的话则为 65,534 个码位),可使用 16 位字来表示一个字符,基本满足全球各个国家和地区的日常文字处理需要。但由于地球文明的多样性以及不断发现和涌现的文字及表意符号,最新的 Unicode 7.0 版本已经定义了超过十万个码位。而由国际标准组织定义的 ISO 10646 统一字符集标准之最新版本,甚至需要 31 位的码位空间才能表达所有的字符。
116 |
117 | 在计算机程序中处理 Unicode 字符时,有多种编码方式。比如对 Unicode 3.0,我们可以使用 16 位字来表示单个字符,对应于 ISO 10646 标准的 UCS-2/UTF-16 编码方式,而针对 Unicode 7.0 等更大的字符集,则需要使用 32 位双倍字来表示单个字符,对应于 ISO 10646 标准的 UCS-4/UTF-32 编码方式。
118 |
119 | 但是 UCS-2 和 UCS-4 这两种编码方式存在一些问题:
120 |
121 | - 在不同的计算机系统上,UCS-2 和 UCS-4 编码方式存在字节序问题。
122 | - UCS-2 编码的数据在 UCS-4 编码的系统上进行处理时,需要重新转换编码。
123 | - 大量使用 ASCII 字符集编码的计算机程序等已有的数据使用单字节编码,若使用统一的 UCS-2 或者 UCS-4 编码方式,将带来极大的内存浪费。
124 |
125 | 为了解决这些问题,Unix、C 语言作者之一肯·汤普逊于 1992 年提出了 UTF-8 编码方式,并成为当前几乎所有互联网内容的主要编码方式。UTF-8 是一种针对 Unicode 的可变长度字节编码,属于一种前缀码。它可以表示 Unicode 标准中的任何字符,且其编码和 ASCII 兼容。其主要特性有:
126 |
127 | - UCS码位在 0x00000000 到 0x0000007F 之间的字符(即 ASCII 定义的字符)被简单编码为单个字节的 0x00 到 0x7F。这样,UTF-8 兼容 ASCII,也就是说,仅包含 7 位 ASCII 字符的文件和字符串在 ASCII 和 UTF-8 编码下的形式是一样的。
128 | - 所有码值大于 0x7F 的 UCS 字符,将被编码为多字节序列,而且其取值范围为 0x80 到 0xFD。这样,ASCII 字符将不会出现在其他字符的编码范围中,而且不会影响空字符等控制字符的行为。
129 | - UCS-4 字符串的词典排列顺序被保留。
130 | - UCS 定义的所有 31 位码位均可被 UTF-8 编码。
131 | - UTF-8 编码不使用 0xC0、0xC1、0xFE 和 0xFF 等字节。
132 | - 单个非 ASCII 字符的 UTF-8 编码的多字节序列中,第一个字节的取值范围始终为 0xC2 到 0xFD,使用高位值为1的位数可给出该多字节序列的长度。其后所有字节的取值范围为 0x80 到 0xBF,这种编码方式在丢失字节的情况下,可保持解码的同步并使得解码过程健壮而无状态。
133 | - 理论上,使用 UTF-8 编码 UCS 字符,其最大长度可达 6 个字节。但由于 Unicode 3.0 标准并未定义码值在 0x10FFFF 之上的字符,因此,Unicode 3.0 字符使用 UTF-8 编码时,其最大长度为 4 个字节。
134 |
135 | 表 4-2 给出了 UCS 码值对应的 UTF-8 编码(二进制形式)。
136 |
137 | 表 4-2 UTF-8 编码
138 |
139 | | UCS 码值范围 | UTF-8 编码(二进制) |
140 | |:-------------------------|:----------------------------|
141 | | 0x00000000 - 0x0000007F | 0xxxxxxx |
142 | | 0x00000080 - 0x000007FF | 110xxxxx 10xxxxxx |
143 | | 0x00000800 - 0x0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
144 | | 0x00010000 - 0x001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
145 | | 0x00200000 - 0x03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
146 | | 0x04000000 - 0x7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
147 |
148 | 在上表中,UTF-8 二进制编码中的 xxx 位取自对应码值的二进制表述。比如版权符号(©)的 Unicode 码值为 0xa9,其二进制表述为 1010 1001,则其 UTF-8 编码为:11000010 10101001,亦即 0xc2 0xa9。
149 |
150 | 需要注意的是,UCS 码值在 0xD800 到 0xDFFFF 范围(即替代码位)以及 0xFFFE 和 0xFFFF 这两个非字符,不会出现在 UTF-8 编码字节流中。
151 |
152 | 下面的 C 代码将给定的 UCS 码值转换成 UTF-8 编码:
153 |
154 | ```c
155 | static int utf8_conv_from_uc32 (UChar32 wc, unsigned char* mchar)
156 | {
157 | int first, len;
158 |
159 | if (wc < 0x80) {
160 | first = 0;
161 | len = 1;
162 | }
163 | else if (wc < 0x800) {
164 | first = 0xC0;
165 | len = 2;
166 | }
167 | else if (wc < 0x10000) {
168 | first = 0xE0;
169 | len = 3;
170 | }
171 | else if (wc < 0x200000) {
172 | first = 0xF0;
173 | len = 4;
174 | }
175 | else if (wc < 0x400000) {
176 | first = 0xF8;
177 | len = 5;
178 | }
179 | else {
180 | first = 0xFC;
181 | len = 6;
182 | }
183 |
184 | switch (len) {
185 | case 6:
186 | mchar [5] = (wc & 0x3f) | 0x80; wc >>= 6; /* Fall through */
187 | case 5:
188 | mchar [4] = (wc & 0x3f) | 0x80; wc >>= 6; /* Fall through */
189 | case 4:
190 | mchar [3] = (wc & 0x3f) | 0x80; wc >>= 6; /* Fall through */
191 | case 3:
192 | mchar [2] = (wc & 0x3f) | 0x80; wc >>= 6; /* Fall through */
193 | case 2:
194 | mchar [1] = (wc & 0x3f) | 0x80; wc >>= 6; /* Fall through */
195 | case 1:
196 | mchar [0] = wc | first;
197 | }
198 |
199 | return len;
200 | }
201 | ```
202 |
203 | 另外,除了 UTF-8 编码之外,UCS 相关的编码还有 UTF-7、UTF-16、UTF-16LE、UTF-16BE、UTF-32、UTF-32LE、UTF-32BE 等。其中的 LE、BE 后缀分别表示小头字节序或者大头字节序。
204 |
205 | ## 4.4 历史上曾使用过的其他文字编码体系
206 |
207 | 在计算机技术发展,尤其是操作系统的发展过程中,在标准缺失的情形下,操作系统厂商或者计算机系统厂商经常为了满足特定地区的市场需求而自行制定一些字符集或者编码体系。如上面提到的 GBK 就是微软公司为中国大陆设计的兼容 GB2312 的字符集,后来成为事实上的标准,并影响了 GB18030 字符集的制定。尽管当前几乎所有的最新软件都开始使用 Unicode 尤其是 UTF-8 编码来处理文字,但仍然有大量资料使用老的字符集标准存储和交换。本小节将简单介绍一下历史上曾使用过的主要字符集及文字编码体系。
208 |
209 | ### 4.4.1 EUC
210 |
211 | EUC 全称为 Extended Unix Code,即扩展 Unix 编码。顾名思义,EUC 和 Unix 操作系统相关。1991 年,Unix 操作系统相关公司制定了 EUC 标准,主要用于存储汉语文字、日语文字以及韩语文字。其中,
212 |
213 | - EUC-CN 是 GB2312 字符集的常用编码方法,亦即GB2312 的默认编码方法。这种编码方法兼容 ASCII。ASCII 字符使用 0x00~0x7F 单字节编码,GB2312 定义的其他字符使用两个字节来编码,第一个字节取值范围为:0xA1-0xF7,第二个字节的取值范围为:0xA1-0xFE。
214 | - UC-JP 是日本 JIS X 0208 和 JIS X 0212 两个字符集的扩展 Unix 编码方法。和 EUC-CN 类似,ASCII 字符使用 0x00~0x7F 单字节编码,而使用两个字节或者三个字节来编码半角片假名(两个字节)、JIS X 0208 定义的字符(两个字节)以及 JIS X 0212 定义的字符(三个字节)。但在 Windows 和 IBM 的日语操作系统中,普遍使用 Shift_JIS 编码方法。
215 | - EUC-KR 是韩国KS X 1001字符集的扩展 Unix 编码方法。和上面类似,ASCII 字符使用 0x00~0x7F 单字节编码,KS X 1001定义的其他字符使用两个字节来编码,两个字节的取值范围均为 0xA1-0xFE。
216 | - EUC-TW 是针对中国台湾CNS 11643 字符集标准制定的扩展 Unix 编码方法。除 ASCII 字符之外,其他字符使用两个字节或者四个字节的来表示。不过,台湾、香港、澳门、新加坡、马来西亚等繁体中文通行的国家和地区广泛使用的是 BIG5(大五码)及其编码方法,几乎不使用 EUC-TW 编码。
217 |
218 | ### 4.4.2 代码页
219 |
220 | 代码页(Code Page)是特定字符集编码的别称,这个名称在微软、苹果和 IBM 的操作系统软件或者计算机系统中使用,如 DOS、Windows、Mac电脑、IBM 大型机等。比如 Windows 操作系统中的 CP936 代码页,对应的就是 GB2312 的 EUC-CN编码表。
221 |
222 | 在Windows 95 等现代操作系统中,内部使用 Unicode 字符集的 UCS-2 编码进行字符的处理。但在 Unicode 尚未被完全普及的情形下,需要一个映射表将已有的 GB2312 等特定字符集编码情形下的文件或者字符串转为 Unicode 进行处理。通过切换不同的代码页,操作系统还可以支持不同的字符集及其编码处理。而在操作系统内部,这种映射表就称为代码页。
223 |
224 | ## 4.5 文字编码的转换
225 |
226 | 从前面的描述中可以看出,计算机系统为表述人类文字走过了一条复杂的道路。尽管当前 Unicode 的 UTF-8 编码已成为互联网上传输文字内容的事实标准,但因为历史原因,我们有大量的文字使用不同的字符集或者编码来保存。在国内,一些老的网站可能仍然使用 GB2312、GBK 等字符集及其编码来保存网页中的文字内容,这种情况下,网页浏览器就需要将这些不同字符集和编码的文字转换为 Unicode 然后再做进一步的处理。
227 |
228 | 在当今流行的 Linux 操作系统中,我们可以使用 iconv 命令完成文字的字符集及其编码转换。比如将 EUC-JS 编码的文本转换为 Shift_JIS 编码的文本,或者转换为 UTF-8 编码的文本。iconv 是一个开源软件,开发者可在 C 程序中调用其接口完成转换,亦可在 PHP、Python 等各种脚本语言中使用经过封装的接口或者模块。
229 |
230 | 显然,我们可以将前述 ISO-8859 系列、EUC 编码以及各种代码页下的文本转换为 Unicode 字符集的某种编码形式。但如果反过来,则可能无法正确转换,比如将某个汉字转换为 ASCII 码是行不通的。
231 |
232 | 在 Unicode 的 UTF-8 和 UTF-16 等编码之间进行转换是相对直接的,下面的 C 语言代码段完成了一个 UTF-8 编码到 UCS4 编码之间的转换:
233 |
234 | ```c
235 | static Glyph32 utf8_char_glyph_value (const unsigned char* pre_mchar,
236 | int pre_len, const unsigned char* cur_mchar, int cur_len)
237 | {
238 | UChar32 wc = *((unsigned char *)(cur_mchar++));
239 | int n, t;
240 |
241 | if (wc & 0x80) {
242 | n = 1;
243 | while (wc & (0x80 >> n))
244 | n++;
245 |
246 | wc &= (1 << (8-n)) - 1;
247 | while (--n > 0) {
248 | t = *((unsigned char *)(cur_mchar++));
249 |
250 | wc = (wc << 6) | (t & 0x3F);
251 | }
252 | }
253 |
254 | return wc;
255 | }
256 | ```
257 |
258 | 对其他字符集编码到 Unicode 之间的转换,由于字符集制定者的设计思路的不同,导致文字的编码转换无法通过一个简单的公式来处理。比如,GB2312 中的汉字字符在设计上遵循按普通话拼音为顺序的原则排列,但在 Unicode 中,汉字字符按笔画数从小到大排列。这样,当我们要将一个 EUC-CN 编码的文字转换成 Unicode 编码时,需要使用映射表。
259 |
260 | 下面的代码段使用 __mg_gbunicode_map 映射表中保存了六千多个 GB2312 字符对应的 Unicode UCS 码值。gb2312_0_char_glyph_value 函数则根据 EUC-CN 编码计算特定 GB2312 字符在 __mg_gbunicode_map 映射表中的索引值。
261 |
262 |
263 | ```c
264 | static Glyph32 gb2312_0_char_glyph_value (const unsigned char* pre_mchar,
265 | int pre_len, const unsigned char* cur_mchar, int cur_len)
266 | {
267 | int area = cur_mchar [0] - 0xA1;
268 |
269 | if (area < 9) {
270 | return (area * 94 + cur_mchar [1] - 0xA1);
271 | }
272 | else if (area >= 15)
273 | return ((area - 6)* 94 + cur_mchar [1] - 0xA1);
274 |
275 | return 0;
276 | }
277 |
278 | const unsigned short __mg_gbunicode_map[] = {
279 | 0x3000, 0x3001, 0x3002, 0x30fb,
280 | 0x02c9, 0x02c7, 0x00a8, 0x3003,
281 | 0x3005, 0x2015, 0xff5e, 0x2225,
282 | 0x2026, 0x2018, 0x2019, 0x201c,
283 | 0x201d, 0x3014, 0x3015, 0x3008,
284 | ...
285 | 0x9eea, 0x9eef, 0x9f22, 0x9f2c,
286 | 0x9f2f, 0x9f39, 0x9f37, 0x9f3d,
287 | 0x9f3e, 0x9f44
288 | };
289 |
290 | static UChar32 gb2312_0_conv_to_uc32 (Glyph32 glyph_value)
291 | {
292 | return (UChar32)__mg_gbunicode_map [glyph_value];
293 | }
294 | ```
295 |
296 |
297 | [^1]: 其实这个方法现在还在用,比如你乘坐电梯时看到的楼层数字。
298 |
299 | [^2]: ASCII:American Standard Code for Information Interchange,美国信息交换标准代码。
300 |
301 | [^3]: 国际标准化组织:International Standard Orgnization,ISO。
302 |
303 | [^4]: 经常被简称为 GB2312 或者 GB2312-80。
304 |
305 | [^5]: 美国标准协会:United States of America Standards Institute,USASI。
306 |
307 | [^6]: UCS:Universal Character Set。
308 |
309 |
--------------------------------------------------------------------------------
/textbook/part-1-chapter-5.md:
--------------------------------------------------------------------------------
1 | # 第 5 章 多媒体:图像、图形及音视频
2 |
3 | 早期的计算机主要通过文字和用户交互。比如,用户使用键盘输入命令或者编写程序,计算机输出文字或符号到屏幕上展示结果。自图形用户界面(GUI)被发明以来,人们可以通过屏幕看到图像、图形,甚至可以欣赏音乐,观看视频,玩游戏等等,极大拓宽了计算机的用途。在计算机技术中,除文字之外的这类信息类型被称为“多媒体(Multi-media)”。本章讲述计算机如何表述多媒体信息。
4 |
5 | ## 5.1 计算机输出设备
6 |
7 | 作为一种电子计算和通信工具,现代的个人计算机(包括我们现在日常使用的智能手机)和用户产生交互时,主要通过人类的视觉和听觉进行,因此其输出设备可划分为视觉输出设备和听觉输出设备两类。除此之外,智能手机也可以产生振动,尽管这种振动在智能手机中仅起到辅助作用,但仍然拓宽了计算机和用户之间的交互途径。在苹果公司 2015 年发布的智能手表产品中,根据不同的场景,智能手表会产生不同方式的振动,从而使得这种交互方式变成了除视觉、听觉之外的另外一种主要交互方式。相信在不远的将来,利用人类味觉、嗅觉等的新式交互方式会得到广泛应用,比如在智能机器人、虚拟现实等新型的计算机产品形态中。
8 |
9 | 本节将简要阐述计算机视觉、听觉输出设备的硬件工作原理。
10 |
11 | ### 5.1.1 计算机视觉输出设备
12 |
13 | 从计算机程序的角度看,计算机的输出设备有两类。一类称为字符输出设备,一类称为图形输出设备。
14 | 字符输出设备出现在计算机的早期发展过程中。那时,人们通过一个个带有键盘和 CRT 屏幕(阴极射线管)的终端通过串口连接到单个大型机(mainframe)上输入程序,执行程序并观察程序的执行结果。人们在键盘上输出的字符会立即显示在屏幕上,而程序的输出则会连续不断地显示在屏幕上;当屏幕上的字符显示满屏之后会自动滚屏显示新的字符。
15 |
16 | 字符输出设备为程序提供了非常简单的接口,在 C 语言中,我们调用 printf 函数,即可向屏幕上输出字符;调用 gets 函数,可等待并获得用户的输出。
17 |
18 | 与字符输出设备不同的是图形输出设备。图形输出设备提供给程序在计算机屏幕上使用不同的颜色绘制任意形状的能力。此时,程序通过操作我们俗称为“显存(Video RAM)”的内存区域来控制屏幕上的每个像素点(Pixel),从而显示文字、绘制图形。
19 |
20 | 尽管我们现在使用的个人电脑之显示屏均为图形显示设备,但字符输出设备仍然以直接或者间接的方式存在于我们的电脑上。比如,当我们打开个人电脑时,个人电脑的 BIOS 系统首先会将电脑的显示器以字符设备方式工作,显示一些有关 BIOS 版本、内存大小等信息,然后是 Windows 系统,此时,显示屏会以图形输出设备的方式工作,并显示图形用户界面。在 Windows 系统中,如果我们运行 cmd 命令,将打开字符终端模拟程序,此时,我们可以键入 DOS 命令,而其中的命令输出则会显示在字符终端模拟程序的窗口中。再比如,我们在 Linux 或者 Windows 系统中使用 telnet、SecureCRT 等登录到远程主机上时,运行其中的程序也以字符方式展示输出,而 telnet、SecureCRT 程序为这些运行在远程主机上的程序提供了一个模拟的字符输出设备。
21 |
22 | 本质上,字符输出设备是任何计算机系统的最基础设施,在完整的计算机系统中,字符输出设备和当前我们常见的图形输出设备共存。显然,字符输出设备主要处理的是文本的显示,而对我们本章所讲的多媒体信息中的图像、图形相关信息,则必须使用图形输出设备。
23 |
24 | 和计算机图形输出设备关联的计算机系统组件主要是显示卡(显示芯片)和显示器。以 PC 为例,显示卡(display adapter)是一块插在PC主机的电路板(或者集成在 PC 主板上)。一般显示卡由寄存器、存储器(Video RAM和ROM BIOS)、控制电路三大部分组成。大部分的图形显示卡都以兼容标准的VGA模式为基础。而在许多便携式智能设备上,用户面对的显示硬件通常是屏幕较小的 LCD 和 LCD 控制器(LCD controller)。
25 |
26 | 不管是 PC 的显示卡还是嵌入式设备的 LCD 控制器,都将有一个显示RAM(video RAM,VRAM)区域,代表了要在屏幕上显示的图像。VRAM必须足够大,以处理显示屏幕上所有的像素。程序通过直接或间接地存取 VRAM 中的数据来进行图形操作,改变屏幕上的显示。许多显示硬件提供从 CPU 的地址和数据总线直接访问 VRAM 的能力,这相当于把 VRAM 映射到了CPU的地址空间,从而允许更快的 VRAM 访问速度。VRAM 组成的内存段,就叫帧缓冲区(Frame Buffer)。
27 |
28 | 通常,CRT 显示器和 LCD 显示屏都是栅格设备,屏幕上的每一点是一个像素,整个显示屏幕就是一个像素矩阵。帧缓冲区中的数据按照显示器的显示模式进行存储,记录了显示屏幕上每一个像素点的颜色值,即像素值。
29 |
30 | 我们知道,计算机以二进制方式存储数据,每位有两种状态(0 与 1)。对于单色显示模式,屏幕上一个像素点的颜色值只需用帧缓冲区中的一位表示,该位为 1 则表示该点是亮点。而在彩色显示模式下,要表示屏幕上像素点的颜色信息,需要更多的位或字节。比如,对于 16 色显示模式,就需 4 位来存储一个颜色值。在 256 色显示模式下,一个像素点占 8 位即一个字节。在 16 位色彩色显示模式下,则需要两个字节来存储一个像素点的颜色值。随着计算机硬件处理能力的提升以及 32 位系统的普及,现在不管是 PC 还是智能设备,基本上均采用 32 位色或 16 位色显示模式,其他的显示模式已经非常少见了。显然,显示一个像素点使用的位数越多,可显示的颜色越多,显示出来的色彩就越丰富,越可能接近真实世界[^1]。
31 |
32 | ### 5.1.2 计算机听觉输出设备
33 |
34 | 计算机听觉输出设备相对来讲比较简单,和收音机、录音机等使用的扬声器(俗称喇叭)或者耳机并无二致。音频(audio)信号通过电磁、压电或者静电效应,使得扬声器的纸盆或耳机膜片振动挤压空气,从而发出声音。
35 |
36 | 当然,计算机中存储的音频信号都是数字化的,所以,和传统的收音机不同,计算机需要将数字化的音频信号通过 DA(数字到模拟)转换器转换成模拟的音频信号,然后再传输给扬声器发出声音。反过来,如果要让计算机录制声音,则需要经过 AD(模拟转数字)转换器将模拟的音频信号(由麦克风拾取)做数字化处理并存储为二进制数据。
37 |
38 | 随着科技的发展,新近又出现了一种骨传导耳机。骨传导耳机直接通过人类头骨、颌骨将声音传导到听觉神经,而不经过耳朵和鼓膜。因此,骨传导耳机不再需要纸盆或者膜片。骨传导耳机可用于听觉有障碍的人群,如老人或耳聋患者。另外对正常人来讲,使用骨传导耳机时,同时也可以听到通过耳朵传入的声音,因此,可以提高佩戴耳机行进过程中的安全性。还有一个好处就是,骨传导耳机不会影响身边的其他人。相信骨传导耳机在未来的虚拟现实、增强现实产品中会有较为广泛的应用,也会逐步替代现有的耳机产品。
39 |
40 | ## 5.2 图像
41 |
42 | ### 5.2.1 位图及相关概念
43 |
44 | 和图形输出设备的显存对应,在计算机程序中,我们使用位图(Bitmap;在某些系统中也称为像素图,Pixelmap)的概念来表示一副静态的图像。
45 |
46 | 我们可以将位图理解成一个二维数组,其中记录了图像中每一个点的颜色信息,这些点称为像素(Pixel)。位图的高度和宽度以像素个数为单位表示。在电子计算机中,图形显示设备(CRT 或 LCD)普遍使用红绿蓝(RBG)三原色来表示一个像素的颜色。RGB 三种颜色分量的取值范围均为 0x00~0xFF,这样,理论上可表达的颜色种类有 224 之多,大大超出人眼能够分辨的颜色数量。假如我们用三个字节来表示一个像素,则高度和宽度分别为 256 的位图,需要 256\*256\*3 = 192KB 字节。
47 |
48 | 除了位图的宽度、高度等信息外,还有一些和位图相关的概念,综述如下:
49 |
50 | - 每像素位数(bits per pixel),也称颜色深度(color depth),简称色深。色深表示位图中每个像素所占的位数。通常来讲,位图的色深可取 1、2、4、8、16、24 等,分别对应于单色、4 色、16 色、256 色、64K 色和 16M 色。同一位图中的各像素位数都相同,这样,使用不同的颜色深度,可表示的颜色数不同,存储每个像素所需要的空间也就不同。
51 | - 每像素字节数(bytes per pixel),通常表示为 bpp。bpp 表示每个像素所占的字节数。当然,这个概念仅仅出现在颜色深度大于等于 8 的情况下。当颜色深度为8时,bpp 为 1;颜色深度为 16 时,bpp 为 2;颜色深度为 24 时,bpp 为 3。需要注意的是,色深为 24 时,在 32 位系统上,为提高处理速度,每个像素会以四个字节为单位进行存储,这样,一次读取一个 32 位的整数即可获得这个像素的像素值。
52 | - 位图每行所占的字节数(bytes per line,也称为 pitch)。pitch 表示位图每行像素所占的字节数。通常来讲,位图的 pitch 一般被圆整为四的倍数。这样,在 32 位系统中装载位图数据之后,可确保位图的每行像素均对齐于四字节边界,便于应用程序优化处理位图像素值。
53 | - 位图的透明像素。某些图像格式(如 GIF),可定义透明色,当程序显示定义有透明色的位图时,要跳过透明像素,而和透明像素不同的像素应该覆盖已有的像素,这样就实现了透明的图片。
54 | - 像素的Alpha值。某些图像格式(如 PNG),可为每个像素点定义 Alpha 值。Alpha 值将参与位图像素值和要覆盖的像素值(已有像素值)之间的运算,使得最终的像素值是位图像素值和已有像素值以某个百分比混合后的像素值。通常,Alpha 值取 0 到 255 的整数值。当 Alpha 为 128 时,最终的像素值将是位图像素值和最终像素值各取一半之后的像素值,这样就实现了半透明效果。随 Alpha 值的不同,混合的效果不同;Alpha 为零时,将忽略位图像素值,而 Alpha 为 255 时,结果像素值就是位图像素值。如果位图中使用逐点 Alpha 值(即每个像素点都具有不同的 Alpha 值),则一半使用 32 位来表示一个像素,R、G、B 各占8位,Alpha 值占 8 位。
55 |
56 | 在早期的个人电脑系统中,用于存储像素的内存相对比较昂贵,且受内存寻址空间大小的影响,使用每个像素均以 RGB 三原色分量来表示的方法会消耗很大的内存。比如上面提到的 129KB 字节的位图,对只有 640KB 可用内存的 DOS 系统来说,显然过大。于是,人们使用其他变通的方法来表达位图,其中最有效的方法使用调色板技术。
57 |
58 | 使用调色板技术时,每个像素的颜色值不再包含直接的 RGB 分量信息,而是一个编号,使用该编号查找一个线性数组,即可获得这个像素对应的真实 RGB 颜色分量,而这个线性数组就是调色板。如图 5.1 所示。
59 |
60 |
61 | 
62 | 图 5-1 位图及其调色板
63 |
64 |
65 |
66 | 通常,调色板由一个足够大的 RGB 数组表示,该数组每个成员含有三个字节,分别表示R、G、B 三种颜色分量。显示位图时,并不能直接使用像素值来表示 RGB 三种颜色,而是首先要以像素值为索引在调色板中查找对应的 RGB 三种分量的值,最终以这个值作为实际的颜色显示出来。当颜色深度为 1 时,调色板通常只有两个入口,分别表示像素值为0和1时的 RGB 颜色分量;而颜色深度为 2 时,调色板通常有四个入口,分别表示像素值为 0、1、2、3 时的 RGB 分量。以此类推,颜色深度为 8 时,调色板通常有 256 个入口。
67 |
68 | 当位图的颜色深度取 16、24 时,就不再采用调色板,而用直接使用像素值来表示颜色分量。比如在色深为 24 位的位图中,每个像素值存储的是直接的 RGB 分量数据,每个分量占 8 位;而在 16 位位图中,RGB 分量分别占5位、6 位和 5 位。如图 5.2 所示。
69 |
70 |
71 | 
72 | 图 5-2 十六位色像素和 RGB 分量
73 |
74 |
75 |
76 | 在计算机中,将一个静态图像显示显示到屏幕上的过程,大致经历如下几个步骤:
77 |
78 | 1) 从图像文件中获得有关图像大小、调色板等信息。
79 | 2) 按照图像文件的存储格式,解码图片,此时在计算机内存中用来表示图像的位图和当前计算机系统使用的具体显示模式无关,因此被称为设备无关的位图(Device-Indepdent Bitmap)。
80 | 3) 将这个位图中的每个像素转换为适配计算机系统当前显示模式像素值,形成设备相关位图(Device-Depedent Bitmap)。
81 | 4) 将设备相关位图逐点、逐行或者整个复制到图形显示设备帧缓冲区的指定内存位置或区域。
82 |
83 | 为了更好地理解设备相关位图和设备无关位图的概念,假设我们有一个使用调色板表示的 256 色位图,要在 16 位色的屏幕上显示。装载调色板以及对应的像素值到内存中时,由调色板和表示图像各像素点的数组表示的位图就是设备无关的。而要显示到屏幕上,我们需要将这个位图中的每个像素点转换为 16 位色形式,即根据设备无关的位图像素值查找调色板,然后将对应的调色板颜色 RGB 分量组装成图 5-2 所示的 16 位色像素值,然后复制到显存中。执行这种像素转换之后的位图就称为设备相关位图。
84 |
85 | ### 5.2.2 色彩空间
86 |
87 | 本章目前为止提到的是 RGB 色彩空间(color space),即通过红、绿、蓝三原色的不同取值来确定一个颜色。除了 RGB 色彩空间之外,业界还使用 YUV、YIQ、CMYK 等其他色彩空间来定义颜色。其中 YUV 色彩空间主要用于电视视频信号的传输,可兼容老式的黑白电视视频信号。YUV 色彩空间中的 Y 指亮度(luminance),即灰阶值(黑白电视使用灰阶值定义),而 U 和 V 表示色调和饱和度,用于定义像素的颜色,即色度(chrominance)。“亮度”通过 RGB 输入信号建立,方法是将 RGB 信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面:色调与饱和度,分别用和 U(Cb)和 V(Cr)来表示。其中,U(Cb)反映的是 RGB 输入信号蓝色部分与 RGB 信号亮度值之间的差异;而V(Cr)反映了 RGB 输入信号红色部分与 RGB 信号亮度值之间的差异。RGB 到 YUV 色彩空间一般具有如下的转换公式:
88 |
89 |
90 |
95 |
96 |
97 | 类似地,在美式电视信号标准 NTSC 中,还使用 YIQ 色彩空间,对应的颜色空间转换公式为:
98 |
99 |
104 |
105 |
106 | 另外,CMYK 色彩空间主要用于印刷业,通过青、洋红、黄和黑四种颜色的不同取值来定义一种颜色。RGB 色彩空间适合于背景为黑色的显示设施,而 CMYK 适合于背景为白色的显示设施。需要注意的是,色彩空间之间可以转换,但严格意义上讲,不同的色彩空间并不重叠,也就是说,不同的色彩空间中的颜色并不是一一对应的,因此色彩在不同的色彩空间转换时,会带来一些失真。
107 |
108 | ### 5.2.3 流行的图像格式
109 |
110 | 上个小节提到的位图,本质上指计算机在显示一个图像(Image)时,在内存中表示这个图像的方法。我们知道,我们让计算机显示一副图片时,对应的操作是打开这个图片文件。比如我们使用数码相机或者智能手机拍摄的照片文件,通常使用 JPG 后缀名,而常见的图标文件,则具有 PNG、GIF 等后缀名。这些文件,就是我们本节要讲述的图像文件格式。
111 |
112 | 图像文件本质上定义的是一个设备无关的位图,包括其高度、宽度以及像素值等信息。为了有效降低存储图像所使用的存储空间,不同的图像文件格式会定义自己特有的编码方式。这些编码方式本质上属于压缩算法。在一副图像中,我们经常会发现有很多相邻的像素具有同样的像素值,比如白色背景上显示一个黑点的图像。针对这种情形,我们就可以使用特定的压缩算法来降低最终图片文件的存储大小,从而节省存储空间和传输带宽。
113 |
114 | 图像的压缩算法又可以分为无损压缩和有损压缩两类。前者表示解压后的位图和原色位图一样,不损失任何像素值信息;后者表示解压后的位图和原始位图不同,会损失原始位图中的像素信息。用于照片的 JPEG 图片格式,使用有损压缩算法,在某些情况下,同一张照片使用 JPEG 格式存储,选择不同的压缩比,其大小可能有十倍之差,粗略观察,人眼可能无法有效分辨其内容的差别。有损压缩虽然会损失数据的精度,但可提高压缩比,非常适合处理图像、音频、视频等多媒体数据。
115 |
116 | #### *1) GIF*
117 |
118 | GIF(Graphics Interchange Format)格式。这种格式采用了一种非常适合于图像数据的压缩算法(LZW算法),但只能用于 256 色位图。GIF 格式采用调色板定义各个像素值对应的 RGB 分量。GIF 格式有两种规范,GIF87a 和 GIF89a。GIF89a 在 GIF87a 规范基础上定义了简单的动画效果,且可定义透明色。目前,我们在微博、微信等应用上看到的一些动画图片(俗称“动图”)或者动画表情,使用的是 GIF89a 定义的 GIF 图片格式。
119 |
120 | GIF 图片文件以 gif 为后缀名,二进制方式存储,其内容依次分为四个区块:
121 |
122 | 1) 签名及版本信息。GIF 文件的前三个字符始终为“GIF”,紧接着是“87a”或者“89a”这三个字符。这种设计有两个好处。第一,便于程序识别文件格式;第二,用于判断 GIF 文件格式所遵循的规范(或版本)。如果是 89a,则表示包含有多张图片,可用于实现动画。
123 | 2) 全局描述信息。其中包含 GIF 图片的宽度(16位无符号整数,小头)、高度(16位无符号整数,小头)、色深(用于确定调色板大小,可能为1、4、8)、背景色在调色板中的索引值(0~256)、全局调色板(RGB 三原色数组)。
124 | 3) 扩展区。扩展区以字符“c”打头,用来定义文本扩展信息(字符串)、应用扩展信息(字符串)、注释扩展信息(字符串),以及 89a 格式的扩展属性,比如动画各帧之间的延迟时间(以 10ms为单位)、是否需要用户输入(点击鼠标或者按下回车键)才开始动画、是否透明等。
125 | 4) 图片描述区。对于动画 GIF,可包含多个描述区。每个描述区以字符“;”打头,其后九个字节是头部信息。其中包含当前帧在整个图片中的偏移位置以及大小,最后一个字节含有多重信息,如这一帧图像是否使用全局调色板以及是否交错存储;若不使用全局调色板,则这个字节中还包含有色深信息,其后便是这一帧图像使用的局部调色板。头部信息之后,包含的是由 LZW 算法编码的图像数据。
126 |
127 | GIF 使用的 LZW 算法本质上是一种通用无损压缩算法,将在本书第三篇“信息的计算机处理”中讲述。交错(interlace)存储情况下,图片中首先保存序号为偶数的水平扫描线像素数据,然后是序号为奇数的水平线像素数据。这种设计,使得我们可以首先看到图片的整体样子,然后再看到完整全貌,这在传输速度比较低的情形下尤其有用。当然,现在网络和磁盘访问速度都很高了,这么做已经没有多大意义了。
128 | 尽管一张 GIF 只能描述 256 种颜色,但这对表情符号来说足够了。对于颜色较为丰富的图片,使用 GIF 格式来存储时,一般通过统计不同颜色的像素数量来确定调色板。对颜色特别丰富的图片,需要做一些近似处理才能生成比较接近原始图的 GIF 图片,当然,这个时候生成的 GIF 图片会出现色彩失真的情形。在互联网上,某些视屏片段会被转换成 GIF 动画格式,以方便传输和展现。色彩失真就会出现在这种情况下。
129 |
130 | 在使用 GIF 格式存储颜色丰富的图片时,为了能更加准确地反映真实颜色,生成 GIF 图片的程序还可以使用一些算法来弥补颜色损失。其中一种常见算法称为“抖动(Dither)”。
131 |
132 | 如图 5-3 所示[^2]。最后一幅图使用 24 位色表示一副渐变图像(如日落时的天空),由于 24 位色情况下的颜色空间足够大,可以真实体现出这幅图的细节部分;如果将这幅图保存为 GIF 格式,则会损失大量的颜色信息,如果不做任何处理,将出现色彩带(Color Banding),即第一幅图所示情形;第二幅图使用抖动算法,用小的混合色块来模拟颜色的渐变过程,从而可以在使用色深为 8 位的 GIF 格式之情形下,仍然能够获得较为接近真实情况的图片。
133 |
134 |
135 | 
136 | 图 5-3 色带及抖动处理
137 |
138 |
139 |
140 | 抖动处理的原理很简单,其目的是使用调色板中的可用颜色的扩散来获得近似效果,此时人眼会自动将扩散的不同颜色混合成新的颜色。如图 5-4 第一张图所示,当红色和蓝色的方块足够小时,图片将变成紫色;而第二张图则通过单色图来模拟出了灰度效果(如使用铅笔或者钢笔绘制的素描图一样)。
141 |
142 |
143 | 
144 | 图 5-4 抖动处理的原理
145 |
146 |
147 |
148 | #### *2) BMP*
149 |
150 | Windows BMP 格式。这是 Windows 系统定义的图像格式,可支持 4、8、16、24 等多种颜色深度,但不支持透明和 Alpha 混合。Windows BMP 格式针对 4 位色和 8 位色支持 RLE(Run Length Encode)编码方式。
151 |
152 | Windows BMP 文件的后缀名为 bmp,其文件格式和 GIF 类似,但不能定义多帧图像,更为简单些。一个 BMP 文件依次分如下几个数据区:
153 |
154 | 1) 文件头部区域。该区域定义了 BMP 图片文件的签名、文件大小以及位图数据在文件中的偏移量。对应的 C 语言结构如下面的清单所示。其中 bfType 始终为 19778,即两个字符“BM”对应的 16 位无符号整数值。
155 |
156 | ```c
157 | typedef struct BITMAPFILEHEADER
158 | {
159 | unsigned short bfType;
160 | unsigned long bfSize;
161 | unsigned short bfReserved1;
162 | unsigned short bfReserved2;
163 | unsigned long bfOffBits;
164 | } BITMAPFILEHEADER;
165 | ```
166 |
167 | 2) 位图头部区域。该区域定义了位图的基本信息,包括位图头部信息的字节长度、位图宽度、位图高度、位面数量、色深、位图数据编码方式、图片大小等信息。对应的 C 语言结构如下面的清单所示。其中编码方式有适用于 24 位色的 RGB 各占三个字节的情况,适合 15 位色、16 位色情形的 RGB 位域(Bit field)方式,以及适合 4 位色、8 位色的 RLE 编码方式。若色深小于等于 16 位色,则其后包含有调色板数组。
168 |
169 | ```c
170 | typedef struct WINBMPINFOHEADER
171 | {
172 | unsigned long biSize;
173 | unsigned long biWidth;
174 | unsigned long biHeight;
175 | unsigned short biPlanes;
176 | unsigned short biBitCount;
177 | unsigned long biCompression;
178 | unsigned long biSizeImage;
179 | unsigned long biXPelsPerMeter;
180 | unsigned long biYPelsPerMeter;
181 | unsigned long biClrUsed;
182 | unsigned long biClrImportant;
183 | } WINBMPINFOHEADER;
184 | ```
185 |
186 | 3) 位图数据。以水平扫描线为单位组织,每条扫描线占用的字节长度被圆整为 4 的倍数,以便优化装载过程。
187 |
188 | RLE(Run Length Encode)是一种非常简单的图像压缩方法。当一条扫描线上有多个像素值连续相同时,即可采用这种编码方法。以针对 8 位色的 RLE 编码为例,该编码首先给出的是其后具有相同颜色的像素数量,然后像素值。比如“0x02 0x00 0x04 0x66”解码后的实际像素值序列应该为“0x00 0x00 0x66 0x66 0x66 0x66”。
189 |
190 | #### *3) PNG*
191 |
192 | PNG(Portable Network Graphic Format,可移植网络图形格式),是 GNU 为了取代 GIF 格式[^3]而定义的图像格式,可支持各种颜色深度,同时支持透明和 Alpha 混合。PNG用来存储灰度图像时,灰度图像的色深最高可达16位,存储彩色图像时,彩色图像的色深可达 48 位,并且还可存储高达 16 位的 Alpha 通道数据。PNG使用从LZ77派生的无损数据压缩算法。
193 |
194 | PNG 是为替代 GIF 格式而生,同时也提供了超出 GIF 很多特性。比如,可以支持高于 8 位的色深,支持 256 级的 Alpha 混合效果。在使用调色板和 32 位色情形下,PNG 可使用 ARGB 颜色分量来表示每个像素值,从而可以定义每个像素独立的 Alpha 半透明混合效果。需要注意的是,PNG 不支持类似 GIF 89a 规范定义的动画效果。2004年末,APNG 规范提出了一个简单的 PNG 的动画扩展实现方案,但并没有流行开来,这主要是因为 GIF 相关的专利随后到期。
195 |
196 | 相比前述的 GIF 和 BMP 格式,PNG 文件格式要复杂很多。PNG 文件的后缀名一般为 png,在程序中我们使用开源的 libpng[^4] 库来解码 PNG 格式文件或者保存图片为 PNG 格式。
197 |
198 | #### *4) JPEG*
199 |
200 | JPEG(Joint Photographic Experts Group,联合图像专家小组)是第一个国际图像压缩标准。使用这种压缩标准保存的图像文件可用来存储色彩非常丰富的图片。因为 JPEG 文件使用有损压缩技术存储像素值,数据压缩比要比 GIF、PNG 等使用无损压缩算法的图像格式高很多,而且其图像的还原度高,因此被广泛使用在数码相机、智能手机等消费类电子产品中。
201 |
202 | JPEG 图片文件通常的后缀名可能为jpeg、jfif、jpg 或 jpe,最为常见的是 jpg。其中 JFIF 是 JPEG File Interchange Format(JPEG 文件交换格式)的缩写,是对 JPEG 压缩图片的一种封装形式,常用于电脑和数码相机、智能手机产品。某些情况下,也使用 ExifJPEG 文件格式。
203 |
204 | JPEG 支持使用 RGB、YUV 色彩空间以及灰度色彩空间来定义像素,并使用了混合型的图像压缩编码方法,即同时使用有损压缩及无损压缩算法。相关的有损压缩及无损压缩算法,我们将在本书第三篇中讲述。
205 | 我们将原始的位图压缩生成 JPEG 文件时,可指定不同的压缩级别,通常为 11 级,以 0—10 级表示。其中 0 级压缩比最高,但图像品质最差;在采用细节几乎无损失的 10 级质量压缩保存时,压缩比也可达 5:1,此时,JPEG 不使用有损压缩算法来压缩图像,其结果类似 PNG。读者可以在 PC 上尝试将某个 BMP 格式保存的图片另存为 JPEG 格式,会发现最终生成的文件可能仅有几百KB。
206 |
207 | JPEG2000 作为 JPEG 的升级版,其压缩率比 JPEG 高约 30% 左右,同时支持有损压缩和无损压缩。JPEG2000 格式有一个极其重要的特征:它能实现渐进传输,即先传输图像的轮廓,然后逐步传输数据,不断提高图像质量,让图像由朦胧到清晰显示。此外,JPEG2000 还支持所谓的“感兴趣区域”特性,利用这一特性,可以指定图像上感兴趣区域具有较高的压缩质量,还可以选择指定的部分先解压缩。但遗憾的是,JPEG2000 并没有取代 JPEG 成为流行的图片压缩格式。
208 |
209 | 在现代计算机系统中,由于 JPEG/JPEG2000 的压缩和解压过程相对复杂,使用软件方式时,压缩或者解压 JPEG/JPEG2000 要占用很大的 CPU 运算能力和内存,因此,JPEG/JPEG2000 的压缩和解压一般通过专门的硬件芯片来完成,尤其在数码相框和智能手机这类消费类电子产品中。在没有硬件支持 JPEG/JPEG2000 的压缩和/或解压的情形下,可使用 libjpeg[^5] 或者 OpenJPEG[^6] 开源软件来完成 JPEG/JPEG2000 的压缩或解压。
210 |
211 | #### *5) 其他图像格式*
212 |
213 | 除了上面提到的流行图像格式之外,在计算机技术发展的过程中,还出现过如下一些图像格式:
214 |
215 | - PCX 是一种由美国 Zsoft 公司所开发的图像文件格式,原本是该公司的PC Paintbrush 软件的文件格式(PCX代表PC Paintbrush Exchange),却成了最广泛接受的 DOS 图像标准之一,然而随着 Windows 的普及,这个图像格式逐渐被 GIF、JPEG、PNG 等取代。
216 | - TIFF(Tagged Image File Format,标签图像文件格式)是一种灵活的位图格式,通过使用标签,可以在单个文件中定义多幅图像和数据,因此尤其适合于文档和书籍的扫描存储,因此,该文件格式也主要用于扫描仪和传真图像。最初,TIFF 只提供单色(二值)图像格式,采用 CCITT Group IV 2D压缩算法,但随着随着扫描仪功能愈来愈强大, TIFF 逐渐支持灰阶图像和彩色图像,且可以灵活定义图像的压缩算法,如 JPEG 或者 LZW 算法。
217 | - WebP 是一种同时提供了有损压缩与无损压缩的图片文件格式,派生自视频编码格式 VP8,是由 Google 在收购 On2 Technologies 后发展而来的图片格式,以 BSD 授权条款发布。WebP 最初在2010年发布,目标是减少文件大小,但达到和 JPEG 格式相同的图片质量,希望能够减少图片文件在网络上的传输时间。2011 年 11 月 8 日,Google 开始让 WebP 支持无损压缩和透明色的功能。根据 Google 较早的测试,WebP 的无损压缩比相同的 PNG 文件可以减少 28% 到 45% 的文件大小。
218 |
219 | ## 5.3 二维矢量图形
220 |
221 | 在介绍本节内容之前,我们首先区分一下图形和图像这两个计算机术语。在计算机技术中,图形(Graphics)泛指一切显示在计算机屏幕上的东西,可以是一副图片,也可以是绘制出来的按钮等形状;而图像(Image)指以像素点数组为组织形式的图形,一般指图片(Picture)。从概念上讲,图形是图像的超集,而图像又可以称为栅格化(Rasterized)图形。
222 |
223 | 本节要讲述的矢量图形(Vectorized Graphics)指使用矢量化的方式来定义形状以及可能的填充方法在内的计算机图形表述方法。矢量图形区别于使用像素数组为形式的图形表述方法。相比图像而言,矢量图形具有如下优势:
224 |
225 | - 通过使用数学方法描述图形的轮廓和填充属性,我们可以随意放大或缩小矢量图形,不会出现放大图像时产生的马赛克效果。
226 | - 矢量化描述的图形比起特定大小的图像来讲,往往可以节省存储空间和传输带宽,加快传输速度。
227 |
228 | 由于矢量图形技术的如上优势,在现代计算机系统中,显示文字时使用的字型通常使用矢量图形来描述,如 TrueType 字体格式(详情可阅本书第四篇“信息的计算机表达”)。另外,几年前流行的 Flash 动画,也是基于矢量图形的。
229 |
230 | 当然,矢量图形的问题也很明显,比如:
231 |
232 | - 由于计算机的图形输出设备并不能直接处理矢量化图形,因此,程序需要首先将矢量图形进行栅格化(Rasterize)处理,转换为给定大小的位图,然后才能显示到屏幕上。但这需要较强的 CPU 或者 GPU 处理能力。
233 | - 在像素密度较小的显示设备上,经过处理的矢量图形会存在锯齿状等变形问题。这也是为什么很多 TrueType 字体文件中,针对小尺寸的字型直接内嵌有点阵字体的原因(见本书第四篇“信息的计算机展现”)。
234 |
235 | 当前网络上最为流行的矢量图形格式是 SVG。SVG(Scalable Vector Graphics 可缩放矢量图形)是用于描述二维矢量图形的一种图形格式,采用可扩展标记语言(XML,见本篇第六章“抽象对象及复杂对象的表述”)。SVG 由 W3C 制定,是一个开放标准。SVG 主要支持以下几种图形对象:
236 |
237 | - 矢量图形对象,包括矩形、圆、椭圆、多边形、直线、任意曲线等。
238 | - 嵌入式外部图形,包括P NG、JPEG 等外部图像以及外部 SVG 等。
239 | - 文字对象。
240 |
241 | SVG 格式有如下优势:
242 |
243 | - SVG 可嵌入到 HTML 页面中,用来替代栅格图形,亦可嵌入 JavaScript 脚本来控制 SVG 对象,从而形成类似 Flash 的动画效果。
244 | - SVG可方便地创建文字索引,从而实现基于内容的图形搜索。
245 | - SVG 图形格式支持多种滤镜和特殊效果,在不改变图形内容的前提下可以实现位图格式中类似文字阴影的效果。
246 |
247 | 清单 5-1 给出了一个简单的 SVG 示例文件[^7]。
248 |
249 | 清单 5-1 SVG 示例
250 |
251 | ```
252 |
253 |
255 |
264 | ```
265 |
266 |
267 | 上面的 SVG 文件定义了两个圆角矩形,边框颜色均为黑色,但一个用红色填充,一个用蓝色填充;蓝色矩形覆盖在红色矩形上,透明度为 0.7。该 SVG 的渲染效果见图 5-5。
268 |
269 |
270 | 
271 | 图 5-5 示例 SVG 文件的渲染效果
272 |
273 |
274 |
275 | 除了上面的矩形(rect)之外,SVG 还定义有其他基本的形状,其中包括线段(line)、折线段(polyline)、圆(circle)、椭圆(ellipse)、多边形(polygon)等。而对复杂的二维图形,一般使用路径(Path)来描述。
276 |
277 | 计算机图形学中的路径就是各种曲线段连接起来的复杂曲线,一般用来定义对象的边界。如果路径定义的图形是封闭的,还可以定义其中的颜色填充模式,如单纯的颜色或者渐变色。图 5-6 给出了一个典型的路径。
278 |
279 |
280 | 
281 | 图 5-6 路径
282 |
283 |
284 |
285 | 在 SVG 中,一个路径由四类线段组成,分别是直线段、二次贝塞尔曲线、三次贝塞尔曲线、圆弧或椭圆弧。图 5-6 中的路径,由直线和椭圆弧/圆弧组成,对应的 SVG 代码如清单 5-2 所示[^8]。
286 |
287 | 清单 5-2 SVG 路径示例
288 |
289 | ```
290 |
291 |
293 |
313 | ```
314 |
315 | 其中,path 元素中的 d 属性给出了路径的生成数据,M、l、a 等参数前缀表示执行的绘制动作。大写的 M、L、A 等给出的坐标是绝对坐标,分别表示移动到给定点、绘制直线到给定点、绘制圆弧到给定点等等,而 l、a 等表示其后给出的坐标是相对坐标。
316 |
317 | 和直线段由两个端点来描述类似,贝塞尔曲线用两个端点以及一个或两个个控制点来描述。图 5-7 给出了贝塞尔曲线的示例。
318 |
319 |
320 | 
321 | 图 5-7 贝塞尔曲线
322 |
323 |
324 |
325 | 显然,借助基本形状和路径,计算机可以表述各种或简单或复杂的二维图形。
326 |
327 | 在矢量图形中,对封闭形状,我们还需要定义其内部的填充模式。SVG 定义有三种填充模式:单色、渐变色和模式。另外,SVG 还定义有剪切、遮罩、组合等操作,以及滤镜操作。SVG 提供的这些机制,足够用来描述复杂的二维图形,结合后面讲述的动画机制,还可以用来展现复杂的二维动画。
328 |
329 | ## 5.4 动画
330 |
331 | 计算机中常见的动画,主要使用两种方法表述。第一种基于静止图像,如前述 GIF89a 格式;第二种基于矢量图形表述,如 Flash 动画或者 SVG 动画。
332 |
333 | ### 5.4.1 基于静止图像
334 |
335 | 使用 GIF89a 格式表述的动画,本质上定义了一系列要连续播放的动画帧,每一帧对应一副图像;当我们快速展示不同的静止图像时,给人脑的感觉就是连续的。GIF89a 定义的最短切换时间为 10 毫秒,也就是说,最高可达到每秒切换 100 副静止图像的速度,而对人类来讲,每秒 25 帧即可达到较为平滑的动画播放视觉效果。
336 |
337 | GIF89a 这个格式的出现距今已有三十年的时间了,但仍然被广泛使用。比如我们现在使用微博、微信时,其中有很多动画表情(俗称“动图”)采用的就是 GIF89a 格式。另外,如前所述,我们还可以将一段视频转换为 GIF89a 格式保存并播放,但由于 GIF 每一帧图像只能包含 256 种颜色,故而会导致色彩出现较为严重的失真现象。
338 |
339 | 类似地,我们也可以用一组连续的 JPEG 图片形成一个动画文件,这就是 Motion JPEG,简称 MJPEG。MJPEG 中的每一帧图像使用 JPEG 格式压缩,常用于数码相机等移动设备中,用来记录动画短片。MJPEG 的另外一个常见应用场合是视频录制器(Video Recorder),这种设备可将模拟或数字电视的视频信号逐帧压缩为 JPEG,并最终保存为 MJPEG。因为 MJPEG 采用的是帧内压缩技术,相邻两帧之间的数据没有关联,因此,可做逐帧的视频编辑,并广泛应用于视频的非线性编辑。也正因为此特点,在使用 MJPEG 时,无法在画面基本不发生变化(如新闻播报节目)的情形下获得更高的压缩率,故而在视频播放领域,主要使用本章后面讲到的 MPEG 等压缩技术。
340 |
341 | ### 5.4.2 基于矢量图形
342 |
343 | 相比上面基于静止图像的动画,基于矢量图形实现动画要复杂一些。不过其原理并不复杂。比如,我们要描述地球围绕太阳的公转运动(圆形物体的椭圆运动),只要描述出地球公转的椭圆轨迹就可以了。渲染动画的程序,根据设定的速度和椭圆的轨迹即可动态计算出地球的位置,并按照当前时间更新地球的位置即可实现动画效果。
344 |
345 | 这种方法除了可以描述物体(对象)的运动轨迹之外,也可以用来描述对象的颜色、透明度、大小等的变化。
346 |
347 | SVG 1.1 版本增加了动画支持。清单 5-3 给出了一个简单的动画示例[^9]。
348 |
349 | 清单 5-3 SVG 动画示例
350 |
351 | ```
352 |
353 |
355 |
404 | ```
405 |
406 |
407 | 清单 5-2 中的 SVG 文档,首先定义了一个矩形,并通过其子元素 animation 定义了该矩形的四个属性:x、y、width、height 的变化,如:
408 |
409 | ```
410 |
412 | ```
413 |
414 |
415 | 上述代码表示,从 0 秒开始,在 9s 的时间内,使矩形的 x 值从 300 改变到 0。类似的,其他的 animation 元素定义了该矩形其他三个属性的变化情况。
416 |
417 | 其后的代码用来控制一个文本字符串(It's alive!)的动画效果。如注释所言,text 元素最初是隐藏的(visibility="hidden")。从第三秒开始,在六秒内:
418 |
419 | - 该元素变为可见,
420 | - 该元素沿视口的对角线连续移动,
421 | - 其颜色从蓝色向深红色变化,
422 | - 该元素从 -30 度向零度(水平)旋转,
423 | - 该元素的字号放大三倍。
424 |
425 | 在定义 text 元素及其动画子元素之前,代码还通过 g 元素重新定义了一个用户坐标系,将原坐标系中的 (100,100) 设置为新坐标系的原点。
426 |
427 | 图 5-8 中的四幅图,分别给出了上述 SVG 动画在初始时、三秒时、六秒时以及九秒时的效果。
428 |
429 |
430 | 
431 | 图 5-8 示例 SVG 动画在关键时间点上的效果
432 |
433 |
434 |
435 | 在清单 5-2 给出的 SVG 动画中,矩形的大小、位置,文本的颜色、角度和字号,在其动画过程中是线性、匀速变化的。但在实际的动画效果中,鲜有这种线性和匀速的变化方式。比如,要实现一个真空中自由落体的物体,其下降轨迹显然要符合牛顿第二定律定义的速度。这时,动画运动轨迹(包括大小、速度等其他参数的变化)的描述,就可以采用前述的路径来表述复杂的运动轨迹。
436 |
437 | 清单 5-4 给出了使用路径定义元素运动轨迹的示例[^10]。
438 |
439 | 清单 5-4 使用路径定义元素运动轨迹的 SVG 动画示例
440 |
441 | ```
442 |
443 |
445 |
468 | ```
469 |
470 |
471 | 清单 5-4 中前面的代码绘制了蓝色的圆弧和三个小圆,用来表示运动路径。之后的代码定义了一个黄色的三角形(也使用路径定义),然后通过三角形的 animationMotion 子元素定义了三角形的动画运动路径(使用 mpath 子元素指向标识符为 path1 的蓝色路径),该动画在六秒内(dur=6s)完成,并且无限重复(repeatCount="indefinite")、自动旋转(rotate="auto")。图 5-9 中的三幅图,分别给出了上述 SVG 动画在初始时、三秒时以及六秒时的效果。
472 |
473 |
474 | 
475 | 图 5-9 基于运动路径的 SVG 动画示例在关键时间点上的效果
476 |
477 |
478 |
479 | ## 5.5 音频
480 |
481 | 如前所述,声音(声波)经过麦克风拾取[^11]后变成音频信号。这时,音频信号是连续的模拟信号,但数字计算机无法处理模拟信号,因而需要经过量化(或数字化)处理。参照图 5-10,音频信号的量化处理包括两种情形:
482 |
483 | - 使用 ADC(Analog-to-Digital Converter,模数转换器)按固定的时间间隔对音频信号做采样,将声波在时间上做离散处理,见图 5-10 B。时间间隔决定了采样频率。
484 | - 每个采样值本身也会被量化做离散处理,通常取 8 位、12 位、16 位、24 位、32 位等二进制离散数字表达,见图 5-10 C。显然,量化位数越多,音频信号的保真度也越高。
485 |
486 |
487 | 
488 | 图 5-10 音频信号的量化处理
489 |
490 |
491 |
492 | 显然,采样频率越高,音频信号的保真度越高;对采样值的量化位数越多,音频信号的保真度也越高。那么,对音频信号而言,采样频率和采样值位数取多少合适呢?
493 |
494 | 这通常取决于不同的音频信号类型。人耳可以听到的声音频率范围在 20Hz 到 20KHz,其中有噪音、语音,也有音乐。比如语音而言,8000 Hz 的采样频率,8 位的采样值量化位数就可以达到电话质量(能听清,且基本能听出来对方是谁)。对音乐而言,采样频率和量化位数都要比较高才行,毕竟很多发烧友的耳朵比指挥家还灵敏,因此,一般使用 44100 Hz 的采样频率,16 位的量化位数记录音乐,这就是所谓的 CD 质量。值得注意的是,44100 Hz 这个采样频率约为人耳可以分辨的声音频率上限(20KHz)的两倍,这是有一定的理论基础的。
495 |
496 | 如果我们将经过上述量化处理之后的数据原封不动地保存下来,就是音频信号的最原始数据了。用这种方式存储原始的音频量化数据的,以 Windows 平台上的 WAV 文件为典型。
497 |
498 | 本质上,WAV 文件提供了音频信号的高保真无损存储,支持多种音频量化位数、采样频率和声道,但其缺点是文件体积较大。比如,如果我们要按照电话质量保存某领导半个小时的讲话,则其原始的音频量化数据大小为:8000 \* 60 * 30 = 14,400,000B,约为 13.4 MB;而如果对半个小时的交响乐演奏以 CD 质量、双声道(立体声)做量化处理,则原始数据大小为:44100 \* 2 * 60 \* 30 * 2 = 317,520,000,约为 302 MB。由于存储容量的持续增加以及互联网带宽的持续提升,这些数据的大小现在看起来不怎么令人惊讶。但是,如果你想在一个小型的音乐随声听里边放上 1,000 首歌曲,每首平均三分钟长占用 30MB 空间,那就需要 30GB 的存储空间;就算在今天,30GB 的存储空间对便携式数码产品也是非常昂贵的。或者想象本世纪初的那几年,互联网带宽大概在 64Kbps,要想下载一首三分钟长(30MB)的歌曲,就要等上十几分钟的时间,这基本上也是没法接受的。
499 |
500 | 要解决这个问题,计算机工程师们首先想到的就是做数据压缩。音频量化数据的压缩有三种手段。
501 |
502 | 第一种手段在采样阶段完成,因为可以完全还原原始数据,所以属于无损压缩。
503 |
504 | 第二种手段类似我们常见的 ZIP/RAR 文件压缩方法,基于原始量化数据进行数据的压缩处理,所以也属于无损压缩,只是相应的压缩算法针对音频数据的特点做了特别设计,所以要比 ZIP 等通用压缩算法更加高效。这种手段的典型音频格式为 FLAC(Free Lossless Audio Codec,免费无损音频编解码器)。
505 |
506 | 第三种手段通过离散傅里叶变换或者其他数字信号处理原理下的数学变换(如小波变换),将时域信号转换为频域信号而压缩数据,属于有损压缩。典型的就是大家熟知的 MP3,见本章 5.7.3 小节“音视频编码技术:MPEG”。
507 |
508 | 本章我们重点看第一种手段。我们之前描述的将模拟音频信号进行量化处理的方法称为 PCM(Pulse Code Modulation,脉冲编码调制)。通常,经过 PCM 处理之后,我们存储的是采样点的绝对量化值,这通常需要一个完整的采样值量化位数来存储。但如果我们观察一个实际的音频波形的话,会发现相邻两个采样值之间的差相对较小;比如,对 8 位的量化位数(取值范围在 [-127, 128] 这个区间内)来讲,相邻两个采样值之间的差大部分会落在 [-16, 16] 这个区间范围内,前面这个区间的数值需要 8 位来存储,后后面这个区间的数值只需要 4 位存储就可以了。这样,我们就可以仅记录第一个采样点的量化数值,然后记录下一个采样点相当于上一个采样点的差就能降低存储空间的占用。这就是 DPCM(Differential Pulse Code Modulation,差分脉冲编码调制)的原理。
509 |
510 | 如果进一步分析,我们还会发现有很多采样点的量化值并没有发生变化,也就是说,可能有很多相邻、连续的采样点的量化值是一样的,这个时候,我们就可以仅记录这些采样点的数量和差分值,而不需要完整记录每个采样点的差分值,进而进一步降低对存储空间的需求。这就是 ADPCM(Adaptive Difference Pulse Code Modulation,自适应差分脉冲编码调制)的原理。一般来讲,相比标准的 DPM,采用 DPCM 可使存储量减少约 25%,而 ADPCM可压缩更多的数据。
511 |
512 | 第二、三种手段中使用的具体压缩算法,将在本书第三篇“信息的计算机处理”中讲述。在现实中,使用第三种手段最常见的就是 MP3 格式,使用 MP3 压缩一首三分钟的歌曲后,大概只需要 5MB 左右的存储空间。除了 MP3 格式之外,还有 Ogg Vorbis、Opus 等开源、无专利限制的音频压缩格式[^12],以及后来出现的 AAC 格式。
513 |
514 | AAC(Advanced Audio Coding,高级音频编码)现在正在取代 MP3 成为新的音乐压缩标准。AAC 最大可容纳 48 通道的音轨,采样频率最高可达 96KHz,并且在 320Kbps 的数据速率下能为 5.1 声道(杜比环绕声音效)音乐节目提供非常高的还原品质。和 MP3 比起来,它的音质更好,还能节省大约 30% 的储存空间与带宽。
515 |
516 | ## 5.6 MIDI
517 |
518 | 我们知道,不论用什么乐器,我们都可以用简谱或者五线谱来记录音乐,其中包括节奏、音高、强弱等。使用不同的乐器演奏同一个的乐谱,虽然其音调和节奏是一样的,但其音色会有显著变化,而音色取决于每种乐器在演奏不同音符时所形成的声波之谐振波。因此,假如我们要对不同乐器演奏的乐曲做数字化处理,我们只要知道音高(也就是频率)、强度(亦即声波的振幅)、节奏(快慢),以及音色(对应的谐振波组成),就可以使用计算机的方法合成出对应的音乐。和矢量图形类似,我们可以将这种音乐的合成方式称为矢量音乐。和矢量图形类似,使用矢量化的音乐表述方式,可以大大节省存储空间,且理论上不存在失真问题。
519 |
520 | 目前广泛使用的 MIDI(Musical Instrument Digital Interface,乐器数字接口)就是基于上述概念发明出来的。MIDI 最早出现于 20 世纪 80 年代,由美国加州的音乐人 Dave Smith 发明。
521 |
522 | MIDI是编曲界最广泛使用的音乐标准格式,可称为“计算机能理解的乐谱”。利用 MIDI,音乐家在家里就可以为大型交响乐团作曲。
523 |
524 | 一般而言,使用 MIDI 作曲时,每个音色(乐器)对应一个音轨,作曲家为每个音轨定义其音高(频率)、强弱(音量)、节奏(时长)等数据,最终形成一个复杂的 MIDI 音乐。除了计算机支持 MIDI 之外,现在的很多电声乐器(如电钢琴),本质上也是MIDI 设备。
525 |
526 | MIDI 当中的不同音色有多种实现方法。一种方法就是利用前述原理,使用谐振波合成的方法来合成某种音色,缺点是音色的还原度低,毕竟特定乐器的谐振波组合是非常复杂的。较为直接的方法就是事先记录特定乐器之不同音符对应的音频信号,然后再根据每个音轨对应的音符强弱和时长合成在一起播放。这种方法是目前普遍采用的方法,主要优点是音色还原度高,另外还可根据情况替代或者更新某种音色(从而形成音色库)。这种方法称为“波表合成”。
527 |
528 | 现代电子计算机(包括智能手机)中都普遍包含有独立的音频处理模块或者控制卡(在个人电脑上称为声卡),其中均包含有 MIDI 功能,可以直接播放 MIDI 音乐。MIDI 音乐通常存储为 MID、RMI 为扩展名的文件。
529 |
530 | ## 5.7 视频
531 |
532 | 计算机中的视频处理,和电视广播系统的视频技术发展密不可分。早先,电视系统中的视频信号是模拟的,主要有 NTSC 和 PAL 两种制式。后来,电视系统开始转向使用数字信号,通过使用数字化的视频信号,观众可以获得更高的画质(更高清晰度),甚至可用来传输三维视频内容。
533 |
534 | 而随着个人电脑和智能手机的普及,越来越多的人开始使用计算机、平板电脑或者智能手机来观看视频内容,甚至可用智能手机拍摄自己的视频内容。本质上讲,计算机可播放的视频信息和数字电视所使用的视频信息没有差别。但在压缩技术、编码方式等方面要比数字电视复杂一些。另外,随着互联网的发展,计算机的视频内容还需要同时满足网络传输的要求,比如在给定的传输速率下,获得高清视频的流畅播放效果。
535 |
536 | 相比音频来讲,视频数据在计算机中的表述方法要复杂很多。接下来我们分几个小节来阐述视频的计算机表述。
537 |
538 | ### 5.7.1 视频相关概念
539 |
540 | 首先是清晰度。我们在使用智能电视播放视频时,经常会看到标清、高清、超清等选项。显然,高清视频的视觉效果要超过标清视频,但需要更高的数据传输带宽才能流畅播放。
541 |
542 | 视频的清晰度(definition)之定义和电视系统有关。和计算机屏幕的分辨率(resolution)稍有不同,电视使用水平扫描线的概念来定义垂直方向的分辨率。如前所述,电视系统传输视频信号时,使用 YUV 或者 YIQ 色彩空间。以 NTSC 使用的 YUV 色彩空间为例,视频信号被分成三部分,第一部分定义了每条水平扫描线上每个像素点的亮度,而第二部分和第三部分(色度部分),在每条水平扫描线上仅给出了一半像素点(每两个亮度像素点对应一个色度像素点)对应的值。这来源于电视信号从黑白向彩色的过渡:彩色信号(色度)信号被夹杂在原始的黑白电视信号中一并传输,彩色电视可正常接收色度信号并显示,但黑白电视可忽略色度信号而仅显示灰度图像。因此,和计算机屏幕的分辨率准确对应的是视频信号的亮度部分所定义的像素分辨率。
543 |
544 | 以 NTSC 制式为准,每一帧图像由 480 条水平扫描线组成,每条水平扫描线上有 720、704 或者 640 个像素点;高清电视(HDTV,High Definition TV)的水平扫描线可达到 1080 条,每条扫描线上有 1920 个像素点,对应的屏幕分辨率为 1920x1080;超高清电视(Ultra HDTV,又称“4K电视”),对应的屏幕分辨率为 3840×2160。与之相关的概念是长宽比(aspect ratio),传统电视的长宽比为 4:3,而 HDTV 的长宽比为 16:9。现代的智能电视,其屏幕分辨率均为16:9,所以在全屏播放传统的 4:3 电视信号时,会出现人变“胖”的情形。
545 |
546 | 另外一个概念是帧率。帧率定义了每秒钟切换静态图像的帧数,通常以 fps(frames per second)来表示。我们知道,要利用人眼的视觉暂留特性,最低要达到 10fps 的帧率才行。电影胶片一般每秒播放 24 帧静态图像,从而可以保证比较平滑的视觉效果。PAL 制式的电视对应的帧率为 25fps,NTSC 制式对应的帧率为 29.97fps,而最新的一些技术已经将帧率提升到了 120fps。显然,增加帧率可获得更加流畅的视觉效果,尤其在播放画面变化很快的视频时,比如在警匪片中经常看到的追车场面。另外,帧率越高,视频的数据量越大,需要更高的数据传输带宽才能流畅播放。
547 |
548 | 还有一个概念和人们为了使用较低数据量达到较高清晰度而采用的技巧有关,即扫描方式。考虑到一个视频相邻两幅静态图像之间的差别通常较小,所以,如果我们让相邻两帧仅包含整个图像中的一半像素,则可以大大降低视频内容的数据量(一半),且视觉效果并不会差太多——人眼是最容易被欺骗的。这就是交错扫描(Interlaced Scan,又称隔行扫描)模式。在交错扫描模式下,第一帧静态画面包含序号为偶数的扫描线像素数据,而第二帧静态画面包含序号为奇数的扫描线像素数据。和交错扫描模式对应的就是逐行扫描(Progressive Scan)模式:所有静态画面中包含有所有扫描线的像素数据。这就是我们经常在智能电视铭牌上看到 1080p 或者 1080i 的区别;前者表示逐行扫描,后者表示交错或隔行扫描。逐行扫描和交错扫描在视觉上的效果还是比较明显的,使用隔行扫描时,观者会感觉图像有明显的闪烁,观看时间长了,会出现比较严重的视觉疲劳。需要注意的是,4K 超高清电视仅支持逐行扫描。
549 |
550 | ### 5.7.2 视频压缩原理
551 |
552 | 和音频类似,视频数据通常会通过压缩技术来降低数据量,否则经过简单的计算就可以知道一分钟的高清视频片段,如果不做任何压缩,则需要大概 8,898M 字节(约为 8GB)的存储空间,这是无法接受的。因此,几乎所有的视频数据都会经过不同方法的压缩,然后再进行存储或传输。最简单的压缩办法就是采用前述小节“动画”中讲述的 MJPEG 技术,即将每一帧静态图像利用 JPEG 技术进行压缩。另外,如果观察常见的视频画面,我们会发现很多视频画面的背景是不变的,典型的如播音员播报新闻的画面。更进一步说,相邻的静态图像之间会有很多共同之处,因此,我们可以利用这一特点在相邻的静态图像之间做压缩处理。首先,我们保存第一帧画面的完整图像,然后,对其后的每一帧,记录和前一帧的差异数据,类似音频处理中的差分脉冲编码。采用这种方法之后,将更进一步降低存储视频信息的数据量。
553 |
554 | 在视频压缩技术中,前一种方法称为“帧内压缩(intraframe compression)”,后一种方法称为“帧间压缩(interframe compression)”。我们将在本书第三篇“信息的计算机处理”中详细阐述视频压缩技术。
555 |
556 | 根据视频内容的清晰度、帧率以及所采取的压缩技术,我们可以确定一个流畅展现一段视频内容所需要的最高传输速度。这里的传输速度指从硬盘、光盘或者网络上获取视频流数据然后播放的速度要求[^13]。这就是视频流数据对应的码率(bit rate),以每秒传输位数(bits per second,bps)为单位表示。需要注意的是,码率表示的是压缩率较低情况下所需要的最高传输速率,而不是最低传输速率。毕竟使用帧间压缩技术,在画面保持不变的情况下,所需要的传输速率可能降低到几乎为零。
557 |
558 | 在网络视频播放环境下,视频码率对应的就是网络带宽,给出了流畅播放某个视频所要求的最高网络带宽。比如,播放分辨率为 1080p、帧率为 25 fps 的高清视频,尖峰时候的网络带宽要求大概为 10 Mbps。
559 |
560 | ### 5.7.3 MPEG
561 |
562 | MPEG(Moving Picture Experts Group,动态图像专家组)是 ISO 与 IEC[^14] 于1988年成立的专门针对运动图像和语音压缩制定国际标准的组织。在已有的 JPEG 标准以及 H.261 视频编码技术基础上,MPEG 最初的工作于 1993 年被接受为 ISO/IEC11172 标准,这就是我们常说的 MPEG-1。
563 |
564 | MPEG-1 主要针对当时的 VCD 光盘和电视清晰度制定了对应的运动图像编码技术。MPEG-1 定义视频码率不超过 1.2 Mbps,而像素分辨率不超过 768x576,支持 1:1、4:3 和 16:9 多种长宽比,还支持从 23.976 Hz 到 60 Hz 间的八种帧率。另外,MPEG-1 也同时支持音频编码,所支持的音频码率为 32 到 448 Kbps 之间。MPEG-1 标准的颁布对后来的 VCD 播放机及 MP3 便携式随身听的市场发展起到了决定性作用。
565 |
566 | #### *1) MPEG-1 视频编码*
567 |
568 | MPEG-1 定义的图像,使用类似于 YUV 的色彩空间;每一帧图像由三个部分组成,第一部分定义亮度,第二、第三部分定义色度,且亮度部分像素点数量在水平和垂直方向是第二、第三部分的两倍。
569 |
570 | MPEG-1 结合前述的帧内压缩技术和帧间压缩技术。MPEG-1 采用 JPEG 技术实现帧内压缩,为了获得更高的压缩比,使用了基于时间的静态图像预测技术来实现帧间压缩。MPEG-1 的数据流中包含如下几种图像编码类型:
571 |
572 | - I-Frame(Intra-coded Image,内编码图像)。I-Frame 使用 JPEG 压缩技术,是自包含的,无需引用其他图像内容。
573 | - P-Frame(Predictive-coded Image,预测编码图像)。如其名称所暗示,P-Frame 记录的是其相对于之前的 I-Frame(以及其他前置 P-Frame)的变化部分,因此,在编码和解码的过程中,要依赖于前一个 I-Frame 及所有的前置 P-Frame。
574 | - B-Frame(Bi-directionally Predictive-coded Image,双向预测编码图像)。如其名称所暗示,B-Frame 被定义为前一张静态图像和后续 I-Frame 或 P-Frame 之间的差异。显然,B-Frame 需要前后各一个 I-Frame 或者 P-Frame 图像才能正确编解码。假想一只球在一个静态背景上从左向右移动,左侧被球遮盖的部分图像包含在后续的图像中。通过 B-Frame,可通过预测后续图像而不是前置图像而获得。这就是使用 B-Frame 的意义。
575 |
576 | 在实际的 MPEG-1 数据流中,使用上述不同的编码类型的图像帧是周期性交替出现的,如图 5-11 所示。
577 |
578 |
579 | 
580 | 图 5-11 MPEG-1 的图像编码类型
581 |
582 |
583 |
584 | 周期性交替使用不同的图像编码类型,是为了在压缩率和随机定位、前进、后退等常见使用场景下获得一个良好的平衡:
585 |
586 | - 大量使用 I-Frame,将导致压缩率过低;
587 | - 大量使用 P-Frame 或者 B-Frame,压缩率变高,但无法在视频流的随机定位、前进、后退等使用场景下尽快重建完整的静态图像。另外,由于使用有损压缩技术,大量使用 P-Frame 或者 B-Frame,还会使图像发生失真或者扭曲变形。
588 |
589 | 故而,在实践中,MPEG-1 的不同类型图像编码以下面的顺序出现:IBBPBBPBB IBBPBBPBB …。这样,视频流的随机访问最小分辨率将是九张静态图像,当帧率为 30Hz 时,大概为 330 毫秒,这是可以接受的。
590 |
591 | 我们提及视频时,通常强调的是运动的图像,却容易忽视视频中包含有可和运动图像同步播放的音频信号这一事实。所以,视频的编码技术,同时需要考虑音频。
592 |
593 | #### *2) MPEG-1 音频编码*
594 |
595 | MPEG-1 定义的音频编码使用三种采样率:44.1KHz[^15]、48KHz[^16] 和 32KHz,均为 16 位。根据编解码器的复杂性和性能,MPEG-1 定义了三层实现,见图 5-12。
596 |
597 |
598 | 
599 | 图 5-12 MPEG-1 的音频编码步骤
600 |
601 |
602 |
603 | 未压缩的音频数据使用 PCM 编码,可经过快速傅里叶变换(FFT)将其从离散的时域信号变换为离散的频域信号。经过 FFT 过滤后,离散频域信号被划分为不重叠的 32 个子频信号,分别记录每个子频信号的振幅。另外,通过调整音质模型,可确定每个子频信号的噪声级别;噪声级别高时,执行低分辨率量化,噪声级别低时,执行高分辨率量化。
604 |
605 | 对于未压缩的音频数据(Layer-1,第一层),使用 PCM 编码,经 FFT 过滤和音质模型调整后的音频数据(Layer-2,第二层),也使用 PCM 编码,而经过第三层处理后的音频数据,使用霍夫曼编码(一种无损压缩技术)。
606 |
607 | 另外,MPEG-1 所定义的音频编码,可支持单声道和立体声,而立体声可以使用两个独立的数据流来定义,也可以使用单个数据流来定义。前者也称为双声道(dual channel),后者称为混合立体声(joint stereo)。
608 |
609 | 在编码音频数据时,MPEG-1 的每一层可使用 16 个固定的码率,从 32 Kbps 到 448 Kbps 不等。第一层和第二层不支持变化的码率,而第三层可支持变化的码率。
610 |
611 | MPEG-1 第三层就是我们熟知的 MP3 音乐压缩格式。如前所述,MP3 的音频数据是经过有损压缩和无损压缩两种技术的混合压缩数据。
612 |
613 | #### *3) MPEG-1 数据流*
614 |
615 | MPEG-1 定义了音频和视频的交错数据流语法。
616 |
617 | 一个语音数据流由帧组成,而帧由音频访问单元组成。每个音频访问单元由多个槽(slot)组成。在第一层,一个槽包含四个字节,而在第二层和第三层,一个槽包含一个字节。每个音频访问单元是可被独立解压并播放的最小数据单元。一个帧中始终包含固定数量的采样点。比如在 48 KHz 的码率下,一帧的播放时间为 8 毫秒;32 KHz 情况下一帧的播放时间为 12 毫秒。见图 5-13。
618 |
619 |
620 | 
621 | 图 5-13 MPEG-1 音频数据流
622 |
623 |
624 |
625 | 视频数据流可分成六个层来看待:
626 |
627 | 1) 序列层(Sequence Layer)指构成某路节目的图像序列,序列起始码后的序列头中包含了图像尺寸、宽高比、帧率等信息。序列扩展中包含了一些附加数据。为保证能随时进入图像序列,序列头是重复发送的。
628 | 2) 序列层之下是图像组层(Group of Pictures Layer),一个图像组由相互间有预测和生成关系的一组I-Frame、P-Frame、B-Frame 图像构成,但头一帧图像总是I-Frame。图像组层的头部中包含了时间信息。
629 | 3) 图像组层下是图像层(Picture Layer),其中包含了 I-Frame、P-Frame 及 B-Frame。在其头部中包含了图像编码的类型和时间参考信息。
630 | 4) 图像层下是像条层(Slice Layer),一个像条包括了一定数量的宏块,其顺序与扫描顺序一致。宏块(Macro Block)是采用余弦变换(DCT)执行有损压缩时的最小处理单元,在 MPEG-1 中,一个静态图像按 16x16(亮度部分)或者 8x8(色度部分)划分成一个个的宏块。
631 | 5) 像条层下是宏块层(Macro Block Layer)。MPEG-1 中仅定义了 4\:2:2 一种宏块结构,而下面要讲到的 MPEG-2定义了三种宏块结构:4\:2:0、4\:2:2、4\:4:4。这个比例表示构成一个宏块的亮度像块和色差像块的数量关系。4\:2:0 宏块中包含四个亮度像块,一个 Cb 色差像块和一个 Cr 色差像块;4\:2:2 宏块中包含四个亮度像块,二个 Cb 色差像块和二个 Cr 色差像块;4\:4:4 宏块中包含四个亮度像块,四个 Cb 色差像块和四个 Cr 色差像块。
632 | 6) 宏块层下是像块层(Block Layer)。如上所述,像块层定义了宏块中的亮度和色差数据块。
633 |
634 |
635 | 
636 | 图 5-14 MPEG-1 的视频数据流
637 |
638 |
639 |
640 | 图 5-14 给出了 MPEG-1 的视频数据量分层解析。
641 |
642 | #### *4) MPEG-2*
643 |
644 | MPEG-2 制定于 1994 年,设计目标是高级工业标准的图象质量以及更高的码率。MPEG-2所能提供的码率在 3-10 Mbps 间,其在 NTSC 制式下的分辨率可达 720X486,在 MPEG-1 基础上,MPEG-2 的音频编码可提供 5.1 声道[^17]和多达七个伴音声道(DVD 可有八种语言配音)。
645 |
646 | 众所周知,MPEG-2 标准颁布之后,DVD 播放器快速取代了 VCD 播放器。由于MPEG-2在设计时的巧妙处理,使得大多数 DVD 播放器也可播放 VCD 盘片。
647 |
648 | 在音频方面,MPEG-2 在 MPEG-1 的基础上增加了低采样频率,有 16 KHz、22.05 KHz 以及 24 KHZ,并支持 5.1 声道和七个伴音声道,此外,MPEG-2 还增加了对前述 AAC 音频编码技术的支持(其采样频率可以低至8 KHz、高至96 KHz,其最多可支持 48 个通道)。
649 |
650 | 在视频方面,MPEG-2 的视频编码方法并未发生本质的改变,但提供了更高品质的数字视频支持,包括更高的清晰度和更快的帧率。另外,MPEG-2 作为 MPEG-1 兼容性扩展,它提供了交错扫描视频的支持以及一些高级特性,如缩放、更丰富的颜色空间格式等。
651 |
652 | #### *5) MPEG-4*
653 |
654 | MPEG-4 于 2000 年成为国际标准。
655 |
656 | MPEG-4 与 MPEG-1 和 MPEG-2 有很大的不同。MPEG-4 不只是具体压缩算法,它是针对数字电视、交互式绘图应用(影音合成内容)、交互式多媒体(WWW、资料聚合与分发)等的整合,以及新的压缩技术的需求而制定的国际标准。
657 |
658 | MPEG-4 标准同以前标准的最显著的差别在于,它采用了基于对象的编码理念,即在编码时将一幅景物分成若干在时间和空间上相互联系的视频音频对象,分别编码后,再经过复用传输到接收端,然后再对不同的对象分别解码,从而组合成所需要的视频和音频。这样既方便我们对不同的对象采用不同的编码方法和表示方法,又有利于不同数据类型间的融合,并且这样也可以方便地实现对于各种对象的操作及编辑。例如,我们可以将一个卡通人物放在真实的场景中,或者将真人置于一个虚拟的演播室里,还可以在互联网上方便地实现交互,根据自己的需要有选择地组合各种视频音频以及图形文本对象。我们现在在大型演艺会场中经常看到的全息技术,就是上述理念的成功运用。
659 |
660 | 与 MPEG-1、MPEG-2 相比,MPEG-4 具有如下独特的优点:
661 |
662 | - 基于内容的交互性。MPEG-4提供了基于内容的多媒体数据访问工具,如索引、超级链接、上传下载、删除等。利用这些工具,用户可以方便地从多媒体数据库中有选择地获取自己所需的内容,通过内容的操作和码流编辑功能,可应用于交互式家庭购物,淡入淡出的数字化效果等。MPEG-4 提供了高效的自然或合成的多媒体数据编码方法,它可以把自然场景或对象组合起来成为合成的多媒体数据。
663 | - 高效的压缩性。MPEG-4 基于更高的编码效率。同 MPEG-1/2 标准相比,在相同的码率下,可获得更高的视觉听觉质量,这使得在低带宽的信道上传送视频、音频成为可能。同时 MPEG-4 还能对多路数据流进行编码。一个场景的多视角或多声道数据流可以高效、同步地合成为最终的数据流。这可用于虚拟现实、三维电影、飞行仿真练习等。
664 | - 通用的访问性。MPEG-4 提供了易出错环境下的容错性,从而可以保证某些非可靠通讯环境下(如无线网络)中的应用。此外,MPEG-4 还支持基于内容的分级特性,即把内容、质量、复杂性分成许多不同级别来满足不同用户的不同需求,从而可在具有不同带宽、不同存储容量的传输信道和接收端展现不同的内容质量。
665 |
666 | 读者应该会记得,在 MPEG-4 标准颁布之后的本世纪前十年,MP4 播放器曾在祖国大地上红极一时。
667 |
668 | ### 5.7.4 其他视频编码技术
669 |
670 | #### *1) H.264*
671 |
672 | 国际上制定视频编解码技术的组织有两个:一个是国际电信联盟,它制定的标准有 H.261、H.263、H.263+ 等;另一个是国际标准化组织,它制定的标准有 MPEG-1、MPEG-2、MPEG-4 等。而 H.264 则是由这两个组织共同组建的联合视频组(JVT,joint video team)制定的数字视频编码标准,所以它既是ITU-T的 H.264,又是 ISO/IEC 的MPEG-4 高级视频编码(Advanced Video Coding,AVC)的第 10 部分。因此,不论是MPEG-4 AVC、MPEG-4 Part 10,还是 ISO/IEC 14496-10,都是指 H.264。
673 |
674 | 相比 MPEG-1/2/4,H.264 的优势有:
675 |
676 | 1) 低码率。和 MPEG-2 和 MPEG4 ASP 等压缩技术相比,在同等图像质量下,采用 H.264 技术压缩后的数据量只有 MPEG-2 的 1/8,MPEG-4 的 1/3。
677 | 2) 高质量的图像。H.264 能提供连续、流畅的高质量图像(DVD质量)。
678 | 3) 容错能力强。H.264 提供了解决在不稳定网络环境下容易发生的丢包等错误的必要工具。
679 | 4) 网络适应性强。H.264 提供了网络抽象层,使得 H.264 的文件可方便地在不同的网络上传输(例如互联网和无线通信网络等)。
680 |
681 | H.264 最大的优势是具有很高的数据压缩比率。在同等图像质量的条件下,H.264 的压缩比是 MPEG-2 的 2 倍以上,是 MPEG-4 的 1.5~2 倍。举个例子,原始文件的大小如果为 88 GB,采用 MPEG-2 压缩标准压缩后变成 3.5 GB,压缩比为 25:1,而采用 H.264后变为 879 MB,从 88 GB到 879 MB,H.264 的压缩比达到惊人的 102:1。正因为如此,经过 H.264 压缩的视频数据,在网络传输过程中所需要的带宽更少,从而也更加经济。
682 |
683 | 随着 2010 年苹果公司宣布在 Safari 浏览器中支持 H.264 而不再支持 Flash 技术,H.264 几乎取代了 MPEG-4 成为 HTML5 Web 开发中的标准视频编码技术。
684 |
685 | #### *2) H.265*
686 |
687 | 作为 H.264 的后续演进版本,H.265保留了原来的某些技术,同时对一些相关的技术加以改进。新技术使用先进的技术用以改善码流、编码质量、延时和算法复杂度之间的关系,以达到最优化。2012 年 8 月,爱立信公司推出了首款 H.265 编解码器,而在仅仅六个月之后,国际电联就正式批准通过了 HEVC/H.265 标准,标准全称为高效视频编码(High Efficiency Video Coding),相较于之前的 H.264 标准有了相当大的改善。值得一提的是,华为公司拥有最多的核心专利,是目前该标准的主导者。
688 |
689 | 在即将到来的 4K 视频中,相信 H.265 将成为主流的视频编码技术。
690 |
691 | ### 5.7.5 常见音视频格式
692 |
693 | 从上个小节的介绍中我们可以看出,某些标准同时定义音视频编码技术,而有些标准仅定义视频或者音频。而在现实中,视频内容中往往同时包含有音频数据。除了音视频数据之外,复杂的多媒体数据流中还需要包含歌词、字幕(多种语言)、章节定义等等内容。在知识产品保护得到极大重视的今天,数据流中还需要包含数字版权认证信息。故而在实践中,出现了多种不同的音视频格式。有些格式由 MPEG 等标准定义,而有些格式是在这些标准形成之前,由操作系统或者软件平台厂商自己定义的,有些使用了自己的音视频编码技术。表 5-1 给出了常见的音视频格式。
694 |
695 | 表 5-1 常见音视频格式
696 |
697 | | 后缀名 | 说明 |
698 | |:---------|:------------|
699 | | MP3 | 使用 MPEG-1 Layer 3 编码技术的音频文件,主要用于音乐、歌曲等。 |
700 | | MPG/MPEG | 使用 MPEG-2 编码技术的视频文件。在早期的 VCD 盘片上,使用 MPEG-1 编码技术的视频文件通常具有 .DAT 后缀名。 |
701 | | MP4 | 使用 MPEG-4 编码技术的音频或者视频文件。 |
702 | | AAC | 使用 AAC 编码技术的音频文件。 |
703 | | OGG | Ogg 是一种容器格式,可封装由 Xiph.Org 基金会维护的各种开放、免费的音视频编码数据流。 |
704 | | AVI、ASF、WMV、WMA |这三种格式是微软公司为 Windows 操作系统开发的音视频格式。WMV/WMA 是目前 Windows 平台上的主流格式,分别用于视频和音频。 |
705 | | MOV | 这是苹果公司为 QuickTime 多媒体平台开发的视频格式。 |
706 | | FLV、F4V | 这两种格式是 Adobe 公司为 Flash 开发的视频流格式,主要用于在 Flash 中嵌入在线视频。F4V 采用 MPEG-4 视频编码技术。 |
707 | | RM、RMVB | 这两种格式由 Real Networks 公司开发,在网络带宽较小的年代曾红极一时。 |
708 | | DivX | DivX 原是由一群黑客为打破美国的 MPEG-4 技术禁运政策而发明的。最初 DivX 使用 MPEG-4 视频编码技术和 MPEG-1 Layer 3 音频编码技术来封装DVD 影片内容,由于大大缩小了文件体积而迅速走红互联网。DivX 现在已发展成为一种数字视频格式,支持MPEG-4、H.264和最新的 H.265 标准的视频,分辨率可高达 4K 超高清。 |
709 | | WebM | 这是由谷歌发展的一种音视频容器格式。其中的视频编码技术采用谷歌自己的 VP8,而音频编码技术采用 Ogg Vorbis。 |
710 |
711 |
712 | [^1]: 在计算机屏幕上显示的图像是否接近真实情况,还和显示屏的显示技术有关,有时同样类型的显示屏,因为电子元器件的漂移,也会带来明显的色差。
713 |
714 | [^2]: 此图取自维基百科“色彩带”词条。
715 |
716 | [^3]:【维基百科】在早期,GIF 文件所用的 LZW 压缩算法是 CompuServ 所开发的一种免费算法。然而令很多软件开发商感到意外的是,GIF 文件所采用的压缩算法忽然成了 Unisys 公司的专利。据 Unisys 公司称,他们已注册了 LZW 算法中的 W 部分的专利,如果要开发生成(或显示)GIF 文件的程序,则需向该公司支付版税。目前,GIF 相关专利已经失效,但在专利失效前曾引起部分开放源代码社区发起“Burn all GIFs”的运动抵制使用 GIF 格式。因此,人们开始寻求一种新技术,以减少开发成本。PNG标准就在这个背景下出现。
717 |
718 | [^4]: [http://libmng.com/pub/png/libpng.html](http://libmng.com/pub/png/libpng.html)
719 |
720 | [^5]: [http://libjpeg.sourceforge.net/](http://libjpeg.sourceforge.net/)
721 |
722 | [^6]: [http://www.openjpeg.org/](http://www.openjpeg.org/)
723 |
724 | [^7]: 该示例取自维基百科“SVG”词条。
725 |
726 | [^8]: 该示例来自于 SVG 1.1 规范描述文档([http://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands](http://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands0)。
727 |
728 | [^9]: 该示例来自于 SVG 1.1 规范描述文档([http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement](http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement))。略有修改。
729 |
730 | [^10]: 该示例来自于 SVG 1.1 规范描述文档([http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement](http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement))。略有修改。
731 |
732 | [^11]: 故而麦克风也经常被称作“拾音器”。
733 |
734 | [^12]: 这些开源、无专利的音视频编码技术(包括 FLAC),现在由 Xiph.Org 基金会管理,其官方网站为 [http://xiph.org/](http://xiph.org/) 。
735 |
736 | [^13]: 这里我们忽略了对视频数据进行解码和解压缩的过程所需要花费的时间。
737 |
738 | [^14]: International Electrotechnical Commission,国际电工委员会。
739 |
740 | [^15]: 即 CD-DA(Compact Disc Digital Audio,光盘数字音频)采样率。
741 |
742 | [^16]: 即 DAT(Digital Audio Tap,数字音频磁带)采样率。
743 |
744 | [^17]: 左右中、两个环绕声道以及一个加重低音声道。
745 |
--------------------------------------------------------------------------------
/textbook/part-1-chapter-6.md:
--------------------------------------------------------------------------------
1 | # 第 6 章 抽象对象及结构化数据的表述
2 |
3 | ## 6.1 语言和区域
4 |
5 | 在第 4 章中,我们了解到了计算机如何处理文字。但通过适当的字符集以及编码,并不能解决所有有关语言的问题。比如拿汉语来讲,有繁简体之分,而且同样的名词,可能存在不同的叫法,如计算机“程序”,在台湾称为计算机“程式”,大量的海外地名、人名也有着不同的翻译方法。就算是同样使用英语的国家,在日期表达、货币符号等方面也存在着诸多差异。比如,美国人习惯使用“May 20, 2015”的方式表达日期,而英国人习惯使用“20 May 2015”的方式。
6 |
7 | 除此之外,大部分语言在书写的时候使用从左向右的习惯,但阿拉伯、希伯来等语言,使用从右向左的书写习惯[^1]。这一切,都为计算机处理语言相关的问题带来了麻烦。
8 |
9 | 最初,计算机系统仅能处理和显示英文,使用 ASCII 字符集基本上就够了。但随着计算机系统在全球各地广泛使用,就需要计算机软件能够根据计算机用户所使用的语言和国家/地区来做适当的处理。典型的就是计算机软件界面的文字需要翻译成特定的语言,而日期、货币等字符串,需要按照当地的习惯来表达。
10 |
11 | 为了解决这个问题,计算机系统引入了一个抽象的概念:区域(Locale)。区域定义了一个特定的语言和国家/地区组合,通过这个组合设置,告诉运行在操作系统之上的应用软件,当前的语言是什么,日期、货币等的表达要符合哪个地区的习惯。具体来讲,区域设置对数字、货币、时间和日期的格式化产生影响。比如对英语(美国)这个区域来讲,这些内容的格式化形式示例如下:
12 |
13 | - 数字:123,456,789.00
14 | - 货币:$123,456,789.00
15 | - 时间:4\:58:32 PM
16 | - 短日期:5/20/2015
17 | - 长日期:Wednesday, May 20, 2015
18 |
19 | 除了以上用户可以直观感觉到的区别之外,区域还对如下软件系统的接口有影响:
20 |
21 | - 正则表达式处理:如字符(尤其是字母)的排序规则、字符分类、大小写转换等。
22 | - 字符串的本地化处理。
23 |
24 | 在 Linux、Windows 等操作系统中,区域的定义通常具有 zh\_CN 这样的形式,其中 zh 是中文/汉语的国际标准代码,CN 是中华人民共和国的国家代码。当计算机操作系统的区域被设置为 zh\_CN 时,系统中的所有软件默认将使用中文简体来显示界面文字,使用“¥”作为货币符号,而日期的表达使用“2015 年 5 月 19 日星期二”这样的形式。如果区域被设置为 zh_TW 时,则使用中文繁体文字,使用中国台湾的习惯用法。
25 |
26 | 表 6-1 给出了常见的的语言和国家/地区代码。这个表中给出的语言和国家/地区代码,现在是国际标准,标准代码分别是 ISO 639 和 ISO 3166。我们一般使用这两种代码的短形式(两个字母),有时也使用长形式(三个字母)。
27 |
28 | 表 6-1 常见语言和国家/地区编码
29 |
30 | | 语言 | 语言代码 | 国家/地区 | 国家/地区代码 |
31 | |:----------|:---------|:----------|:--------------|
32 | | 中文/汉语 | zh (zho) | 中国 | CN (CHN) |
33 | | | | 台湾 | TW |
34 | | | | 香港 | HK |
35 | | | | 澳门 | MC |
36 | | 英语 | en (eng) | 美国 | US (USA) |
37 | | | | 英国 | UK |
38 | | 法语 | fr | 法国 | FR |
39 | | | | | |
40 | | 德语 | de | 德国 | DE |
41 | | | | | |
42 |
43 | 在计算机软件系统中,一些基础的功能模块,尤其是上述数字、货币、时间、日期的格式化功能函数,其行为受区域设置的影响,会根据当前的系统区域设置来相应调整输出。
44 |
45 | 在现代操作系统中,一般通过环境变量来设置区域。程序运行时,可继承全局的环境变量,亦可动态修改自己的环境变量。在 Linux 系统中,用于设置区域的环境变量称为 LC\_ALL[^2] 或者 LANG,默认情况下为 en\_US.UTF8 形式,其中的 .UTF8 后缀,指明了文字的字符集及编码。
46 |
47 | 另外,具有良好国际化和本地化设计的计算机软件,会根据当前的区域设置来显示不同的界面文字,实现界面文本的自动适配。
48 |
49 | 计算机软件的国际化和本地化是两个概念。首先,计算机程序中使用字符串时,要经过一次处理,这个处理通常是一个函数,该函数可根据一个唯一的标识符来返回当前区域对应的字符串,然后再行使用这个字符串。这个处理称为“国际化(internationalization[^3])”。其次,需要针对不同的区域增加对应的字符串集合,这个过程称为“本地化(localization)”。
50 |
51 | 在 Linux 操作系统中,一般直接使用 en\_US 区域下的字符串作为获得本地化字符串的唯一标识符,传递给字符串转换函数,该函数根据当前的区域设置,查找一个映射表,然后返回对应的本地化字符串。我们经常使用 GNU 的 gettext 开源软件完成这项工作,同时使用 msgfmt 工具来完成默认字符串和本地化字符串之间的映射关系,最终生成一个 mo 文件,保存在以区域为名称的子目录(如 zh\_CN)下。若 gettext 无法找到给定区域的字符串映射表时,就会返回原字符串。这样,在没有针对这个区域完成本地化的情况下,程序的输出字符串仍然为原字符串。
52 |
53 | 在 Windows 操作系统中,可以使用 GNU 的 gettext 工具实现国际化和本地化,但 Windows 开发工具提供的方案是使用整数的字符串标识符作为字符串对象的唯一标识。
54 |
55 | ## 6.2 时间、日期及时区
56 |
57 | 在计算机系统中,我们看到的时间和日期是“2015 年 5 月 20 日 18:54”这种形式,然而计算机内部的计时系统并不直接记录上面这种形式的时间和日期,而是以秒为单位的一个整数,这个整数记录了从 1970 年 1 月 1 日 00:00 UTC[^4] 开始到当前时间的秒数,这个秒数被称为“UNIX 时间戳”,而起始的“1970 年 1 月 1 日 00:00 UTC”这个时间被称为 UNIX 纪元时间(UNIX Epoch)。
58 |
59 | 操作系统在启动时,首先会从计算机系统的底层硬件时钟当中获得当前的时间,以此为基础换算为 UNIX 时间戳,然后根据时钟芯片产生的滴答中断(一般每秒 100 次)来维护时间戳向前增加。
60 |
61 | 在 32 位计算机系统中,操作系统最初维护的时间戳是一个 32 位的无符号整数。经过简单计算我们可知,这个 32 位无符号整数记录的时间戳将在 2038 年的某一天溢出。为提前应对这个问题,现代操作系统的新版本开始使用 64 位无符号整数来记录 UNIX 时间戳,同时也可提供毫秒甚至微秒级的时间信息,以便需要高精度时间数据的应用程序使用。
62 |
63 | “2015 年 5 月 20 日 18:54”这种形式的日期和时间表达方法,在计算机系统中称为“墙钟(Wall clock)”,应用程序可根据操作系统维护的当前时间戳以及时区设置计算出墙钟日期和时间。
64 |
65 | 系统时区的设置和上个小节提到的区域类似,使用 TZ 环境变量(在 Linux 操作系统中,若未定义 TZ 环境变量,则使用 /etc/localtime 中的数据),可通过操作系统的编程接口来获得或设置。
66 |
67 | 一个给定的时区可以有多种表达方式:
68 |
69 | - 使用标准名称,如北京时间的标准名称为 CST(China Standard Time)。
70 | - 使用标准名称及相对 UTC 的偏移量,其格式为 \\。如 CST-8\:00:00,其中 CST 是北京时间的标准名称,-8\:00:00 表示本地时间减去八个小时为 UTC。
71 | - 使用 GMT\ 格式。其中 GMT 为格林威治时间(亦即 UTC),OFFSET 表示对应的本地时间和 GMT 的偏移量。比如 CST 还可以表达为 GMT+8\:00:00。
72 | - 根据所在地区和城市确定时区,比如 Asia/Shanghai 或 America/Chicago 等。
73 |
74 | 若给定的时区支持夏令时,会给计算机系统处理本地时间带来更多的麻烦,而且时区的定义可能会发生变化(比如朝鲜就在 2015 年宣布使用自己全新定义的时区,新的朝鲜标准时间比北京时间早 30 分钟,而之前是和北京时间一致的)。为此,现代计算机系统一般会提供最新的时区信息文件,在这些文件中保存有完整的时区描述数据,比如标准名称、UTC 偏移量、是否支持夏令时以及夏令时的起止时间等。在 Linux 系统中,这种文件称为 tzfile;通过使用 TZ 环境变量可指定从特定的 tzfile 中获得对应的时区数据,比如新西兰,使用 TZ=":Pacific/Auckland" 的定义 TZ 环境变量,则对应的时区信息保存在 /usr/share/zoneinfo/Pacific/Auckland 文件中。
75 |
76 | 在现代计算机系统中,获取或设置时间戳的接口一般由操作系统内核提供。以 Linux/Unix 为例:
77 |
78 | - time () 函数返回当前的时间戳(以秒为单位);
79 | - gettimeofday/settimeofday 可以以微秒级精度来获取或设置当前的时间戳;这两个函数亦可用来获得或设置当前的时区[^5];
80 | - 后来引入的 clock_gettime/clock_settime 函数可以以纳秒级精度来获得或设置当前的时间戳。
81 |
82 | 在以上的操作系统内核接口之上,根据时间戳计算得到某个特定时区下的具体日期及时间的功能,则由应用软件负责完成。在不同的编程语言下编程时,相应的平台会提供一些公共接口来帮助应用程序完成日期的计算、对比和转换等工作。比如在使用 Java 语言时,可使用 Calendar 类计算给定时间戳对应的分解时间,具体包括年份、月份、日期、当年第几周、当年第几天等等信息,再结合当前的区域设置,经过字符串的格式化操作,即可得到想要的日期及时间字符串(如“2015-08-27 11:17”)。在使用 C 语言时,C 语言的标准函数库也提供了 ctime、gmtime、localetime、mktime、strftime 等函数来完成时间戳到分解时间的计算,以及时间字符串的格式化。
83 |
84 | 当然,我们这里提到的日期都是公历(也称作格列高利历,即 Gregorian Calendar)。如果要显示中国的农历日期,则需要额外的程序执行公历到农历的转换,而这种转换功能并不是软件平台标准的一部分,毕竟这些标准都是西方国家设计的,没几个人懂农历。
85 |
86 | 值得一提的是,计算机硬件系统保存的时间和电子/石英手表一样,使用晶振来计时,时间一长,就可能出现大的误差,比如每过 24 小时会快上几秒或者慢上几秒。随着互联网的普及,操作系统开始使用互联网获得更为精确的时间。互联网上有众多提供标准时间的服务器,称为“时间服务器”,通过网络时间协议(NTP)或浮动时间同步协议(FTSP),任何访问这个服务器的系统都可以精确获知当前的时间。这样,很多操作系统在启动时使用上述协议重置系统时间,同时周期性地和时间服务器同步系统时间,以弥补硬件计时系统可能存在的误差。
87 |
88 | ## 6.3 HTML
89 |
90 | HTML 是 Web 页面的唯一描述语言,由 Web 发明者 Tim Berners-Lee 于 1989 年发明并逐步演进到今天广为接受的 HTML5。
91 |
92 | 在 Web 被发明之前,互联网用户主要通过电子邮件(email)、文件传输协议(FTP)等机制协同工作。在 CERN(欧洲核子研究组织)工作时,Tim Berners-Lee 开发了一个简单的 HTTP 服务器以及一个 WWW(World Wide Web,万维网)客户端程序。这个程序可以从 HTTP 服务器中获得一个 Web 页面文件,然后根据这个文件中定义的标签展示结构化的页面内容,其中包括主题、标题、段落等等。那时候,一个典型的 HTML 页面内容大概如清单 6-1 所示。
93 |
94 | 清单 6-1 一个最简单的 HTML 文件内容
95 |
96 | ```
97 |
98 |
99 |
100 |
101 |
102 | Hello, World!
103 |
104 |
105 |
106 |
107 |
108 |
109 | HELLO, WORLD!
110 |
111 |
112 |
113 | This is the first Web page in HTML.
114 |
115 |
116 |
117 | Click HERE to see the second Web page.
118 |
119 |
120 |
121 |
122 |
123 | ```
124 |
125 |
126 | 使用我们现在常用的 Web 浏览器(如 Chrome)渲染上述 HTML 内容,其显示效果如图 6-1 所示。
127 |
128 |
129 | 
130 | 图 6-1 一个最简单的 Web 页面渲染效果
131 |
132 |
133 |
134 | HTML 是 Hyper Text Markup Language 的缩写,中文译为“超文本标记语言”。HTML 的基础语法来自于 1986 年由 ISO 标准化组织颁布的 SGML(Standard Generalized Markup language,标准通用标记语言)。仔细对比清单 6-1 中的内容和图 6-1 中的显示效果,我们可以看到:
135 |
136 | HTML 中由尖括号(<>)包围的称为标签(tag),标签往往成对出现,定义了一个要显示在页面中的元素(element)。如 \ 表示开始定义文档的主题,\表示结束文档主题的定义,这两个标签之间是主题的内容“Hello, World!”。类似地,H1 标签表示的是文档的一个一级标题,P 标签标识的普通段落。如本文档仅包括一个一级标题,其内容为“HELLO, WORLD!”;其后是两个普通段落。
137 |
138 | 除了上述具有实质性内容的标记之外,还有 HTML、HEAD、BODY 等定义文档整体结构的标签。其中 HEAD 定义了文档的头部信息,TITLE 标签包含在其中,而 BODY 标签定义了文档的正文内容,所有显示在浏览器页面中的元素定义在 BODY 标签中。
139 |
140 | 另外本示例还包含有一个重要的标签 A,该标签定义了超链接(Hyper Link)。在浏览器中,超链接显示为带有下划线的蓝色文字,当用户点击超链接时,将告诉浏览器跳转到另外一个页面。这个页面可能是由同一服务器提供的,也可能是由其他服务器提供的。
141 |
142 | 总而言之,HTML 通过标签定义了复杂的 Web 页面的文档结构,这些 Web 页面由浏览器处理并展现,并通过超链接将分布在世界各地的 Web 页面链接在了一起,这是 Google、百度等巨型互联网公司赖以出现并发展壮大的最重要技术基础。
143 |
144 | 在之后二十多年的发展中,HTML 从最原始的二十多个标签发展为近百个标签,同时引入了 CSS(级联样式表)技术来灵活定义文档元素的渲染效果,还引入了 JavaScript 编程语言来动态控制页面的内容。随着 HTML5 标准的正式发布,硬件处理性能的提高,大部分人们开始倾向于认为未来将是 Web App 的天下;2015 年,由优秀华人计算机专家宫力博士发起创立的 Acdemic 公司,开始面向全球开发和推广基于 HTML5 技术的操作系统:H5OS。这一切预示着 HTML5 及其相关标准和技术在未来可能具有不可限量的发展空间。为此,本书将分别在第五篇“信息的计算机展现”和第六篇“计算机编程语言”中专门讲述使用 HTML5、CSS3、JavaScript 相关技术来开发 Web 应用的基本概念及方法。
145 |
146 | 在今天我们看来,互联网之所以获得巨大成功,和 Web 以及相关技术(如 HTML)的发明有着绝对的直接关系。尽管 Tim Berners-Lee 并没有从 Web 的发明中获得直接利益,但时至今日,他仍然作为 W3C(万维网联盟,Web 相关标准的制定者)发起人和主席参与相关标准的制定,指挥着万维网的发展。
147 |
148 | ### 6.3.1 HTML 的演进
149 |
150 | HTML 从第一个版本到现今广为接受的 HTML5,走过了二十多年的历程,期间出现了许多 HTML 的版本,也出现了很多相关的标准或规范[^6]。有意思的是,尽管这些标准基本上都是出自 W3C 之手,但有些标准和规范并未获得业界的广泛接受。更有意思的是,就算是同一个标准,很多厂商(主要是浏览器厂商)并没有按照统一的标准和规范要求做相应的实现,有些厂商甚至刻意在其软件实现(浏览器)中引入了不兼容。
151 | 比如 XHTML,这个标准被戏称为 HTML 的 XML 版本;XHTML 和 HTML 并没有本质上的区别,但 XHTML 要求按照 XML 的语法来严格书写 HTML,比如标签和属性必须使用小写,属性值必须使用双引号(")包围,空标签必须使用 \
这样的形式,不能省略结束标签(如 \),不能随意嵌套元素等等。然而,XHTML 并未被广泛接受,因为虽然有大量的网页并不符合这个标准的要求,但浏览器仍然可以正常处理而不会报任何错误,于是,没有开发者愿意花额外的时间来做检查并修复不符合规范要求的写法,况且很多 HTML 代码是由程序自动生成的,还涉及到程序的修改,其工作量可想而知。之所以提出 XHTML 这个标准,现在看来,大概是因为标准制定者的心理洁癖。目前,W3C 仍然致力于 XHTML 标准的演进,在颁布新的 HTML 标准之时,都会相应颁布对应的 XHTML 版本。比如HTML 4.01 对应的是 XHTML 1.0;HTML5 对应的是 XHTML5。
152 |
153 | 现代浏览器在处理从 HTTP 服务器端获得的网页时,根据内容类型(Content Type)来决定使用 HTML 的规范要求还是使用 XHTML 的规范要求。当服务器返回的内容类型为 text/html 时,浏览器按照 HTML 处理,不要求严格的语法,不会报任何语法错误;当内容类型为 application/xhtml+xml 时,则按照 XHTML 处理,浏览器会做严格的语法检查,存在任何不符合 XML 语法要求的情况均会报错。这大概是一种折衷处理办法,毕竟从长远看,符合标准的严格语法要求有利于长期发展,但面对现实情况又不得不做出让步。
154 |
155 | 在 HTML 4.0 提出之前比较成熟的标准是 HTML 3.2,该版本颁布于 1997 年 1 月。HTML 3.2 及之前的版本主要使用某些格式标签,如 font(字体)、big(大字体)、strike(删除线),以及一些属性,如 color(颜色)等定义文本的样式变化。但这种标签和属性的大量使用一方面导致 HTML 代码看起来非常混乱,另外一方面和 HTML 仅定义文档结构的初衷相背离。为解决这一问题,W3C 逐步引入了 CSS 的概念。
156 |
157 | 使用 CSS,我们可以将文档中的元素和该元素对应的渲染样式隔离开来。具体来讲,就是我们可以针对 HTML 文档中的某个特定元素或某类元素单独定义其渲染样式。一个 CSS 样式可定义一个元素的尺寸、边框、背景、前景色、字体、对齐方式等等样式属性,而且可以单独存放在 CSS 文件中。CSS 的发明给 Web 开发带来了非常深远的影响,大受 Web 开发者的欢迎。
158 |
159 | 然而,真正成熟和广泛接受的 HTML 4.01 版本是在 1999 年颁布的,对应的 CSS 版本是 CSS 2.0。在这之前,从 HTML 3.2 开始,CSS 1.0/1.2 已经得到一些浏览器的支持,好在并没有太多开发者使用,那时用户使用的浏览器绝大部分是微软的 IE(Internet Explorer),因此,向更高版本标准的演进并没有导致太多的麻烦。遇到真正的麻烦的是 CSS 3.0。CSS 3.0 引入了更加清晰的布局概念,同时也引入了 CSS 动画支持。从 CSS 2.0 向 CSS 3.0 演进的过程,刚好是 IE、FireFox、WebKit、Opera 等浏览器混战的年代,于是,在 CSS 3.0 标准的草案还在讨论当中时,这些浏览器就开始部分或者全部支持了 CSS 3.0 的特性,于是,我们看到很多 CSS 样式表中包含这样的写法:
160 |
161 | ```
162 | -webkit-box-shadow:0 2px #135389;
163 | -moz-box-shadow:0 2px #135389;
164 | -o-box-shadow:0 2px #135389;
165 | box-shadow:0 2px #135389;
166 | ```
167 |
168 |
169 | 上面这四行其实是在重复一个属性的定义,不过前三行具有不同的前缀,对应于不同的浏览器:WebKit、Mozilla(FireFox)、Opera。等到 HTML5 标准颁布并获得这些浏览器的支持之后,只要保留最后一行就可以了。
170 |
171 | 上面的例子只是一个简单的例子,真正令开发者头疼的是浏览器在某些方面的特性支持不一致带来的问题。比如,由于 Windows 操作系统的垄断地位,微软在 Internet Explorer 的开发中就引入了很多不符合标准的特性,最为著名的就是 ActiveX 控件。ActiveX 控件使用 Windows 平台特有的接口实现,如果某个网页使用了 ActiveX 控件,就无法在运行于 Unix/Linux 平台上的浏览器上正常展示或正常工作,最常见的就是各种网络银行网站。在最新的 W3C 标准的支持程度上,微软的 IE 始终比其他浏览器的表现差很远。当然,这给 FireFox、Opera、Chrome 等浏览器以机会来抢占市场份额。不过对 Web 开发者而言,Windows 平台上的 IE 浏览器是不能被轻易忽略的——因为其长期以来占据的显著市场份额。这导致了 Web 开发成本的上升,也阻碍了新标准的实施。
172 |
173 | HTML 标准的演进过程是一个典型的计算机软件标准发展过程,它反映了标准演进过程一些经常遇到的问题:
174 |
175 | 1) 标准无法具有足够的前瞻性,总是很难及时满足飞速发展的市场需求,这导致大部分标准形成于事实存在之后,如 CSS 以及 HTML5 新增的画布标签等等。
176 | 2) 某些势力会利用其垄断地位刻意在产品中引入和标准不兼容的特性,如 IE 中的 ActiveX 控件;另外,旧势力(如微软 IE)的存在,会极大阻碍新标准的推广。
177 |
178 | 另外,技术的发展会让某些技术变得过时或者被淘汰,比如,很早出现的 Java Applet 技术最终被 JavaScript 取代,而 HTML5 多媒体标签的出现让 Adobe Flash 插件变得过时。
179 |
180 | ### 6.3.2 HTML5
181 |
182 | 1999 年颁布的 HTML 4.01 可以说是 Web 开发中的一个重大转折点,这主要得益于 CSS和 JavaScript 技术的成熟。使用 HTML 4.01 开发的 Web 应用,可以获得更美观的排版效果以及动态特性。然而,HTML 4.01 仍然存在一些不足:
183 |
184 | - DIV 标签的滥用。为了和 CSS 配合形成美观的排版布局效果,大部分网页使用了大量无意义的 DIV 标签。这背离了 HTML 仅定义文档结构的出发点。
185 | - 在网页中嵌入音频、视频播放器时,仍然要通过插件来进行,比如使用 Adobe Flash 来播放视频。这需要用户在浏览器之外安装额外的插件,同时增大了 Web 开发的工作量。
186 | - 仅仅操作 HTML 文档内容,无法开发出精细的二维或者三维图形应用,比如大型网游。当然,开发者可以自行开发插件来提供相应的功能,但一旦开发自己的插件,就要针对不同的浏览器开发出对应的插件版本。
187 | - 仅仅依赖于 HTTP 协议,很难开发实时联网类应用,如实时聊天室。这里需要说明的是,HTTP 本质上是一种无状态的短连接协议。
188 |
189 | 本质上,HTML5就是为了解决上面这些问题而出现的。比较有意思的是,从 HTML 4.01 到 HTML5,中间经过了漫长的十五年!中间这段时间 W3C 干嘛去了呢?
190 |
191 | 这和之前提到的标准制定者的心理洁癖有关。颁布了 HTML 4.01 之后,W3C 采取了一个错误的路线,一心想把 Web 开发向 XML 方向上引导,所以制定了 XHTML 1.0、1.1、2.0 等标准和规范。尤其是 XHTML 2.0,这个版本一方面更加 XML 化,另一方面和之前的 XHTML 1.0/1.1 都不兼容,这显然是在闭门造车嘛。所以浏览器厂商不买 W3C 的账(其实开发者也不买 W3C 的账),于是浏览器厂商在 Opera 的牵头下另起炉灶办了一个超文本应用技术工作组,也就是 WHATWG。WHATWG 向后来的 HTML5 演进,而 W3C 则在错误的路线孤注一掷。
192 |
193 | 2006 年,W3C 主席(Tim Berners-Lee)认识到了 W3C 的错误,开始主动向 WHATWG 示好,并在 WHATWG 的工作基础上组建了一个新的 HTML 工作组。2009 年,W3C 宣布终止在 XHTML 方向上的工作。
194 |
195 | 虽然有过几年的时间,WHATWG 和 W3C 分头向 HTML5 演进,但最终两家合在了一起。2014 年 10 月 28 日,HTML5 正式成为 W3C 的推荐标准。
196 |
197 | HTML5 的主要特点如下:
198 |
199 | - 在 HTML 4.01 基础上废弃了一些用于定义样式和格式的标签或属性,进一步强化 HTML 仅用于定义文档结构的特征。废弃的标签有 basefont(基础字体)、big(大字体)、center(居中)、font(字体)、s(小字体)、strike(删除线)、u(下划线)、frames(框架)、frameset(框架集)、noframe(无框架)等。废弃的属性有 align(水平对齐)、bgcolor(背景色)、height(高度)、width(宽度)、valign(垂直对齐)等。
200 | - 新增 article(文章)、header(页眉)、footer(页脚)、section(小节)、nav(导航)、aside(边栏)、hgroup(标题组)等标签,可以用于定义文档中的不同类型的段落或者页面区域,这些标签的恰当使用,同时可以防止 div 标签的滥用。
201 | - 新增 canvas(画布)标签,Web 应用可使用 JavaScript 和 WebGL 接口在指定的画布上绘制二维或者三维图形。
202 | - 新增 audio(音频)、video(视频)标签,Web 应用可在网页中嵌入音频和/或视频播放器。
203 | - 通过 JavaScript API 提供了本地存储相关接口。Web 应用可在浏览器端保存数据;键值对形式或者数据库形式。
204 | - 通过 JavaScript API 提供了地理位置相关接口,Web 应用可获知用户所在的地理位置信息。
205 | - 通过 JavaScript API 提供了套接字支持,从而使得 Web 应用可以和服务器端建立持久的套接字连接(区别于基于 HTTP 的无状态短连接)。
206 | - 支持离线 Web 应用。
207 | - 为表单元素提供了更好的类型支持和本地检测机制。
208 |
209 | 显然,HTML5 标准制定者的目标很明显,通过相关标准和规范的实施,基于 HTML5 的 Web 应用最终将不仅仅限于展示网页:它本可以开发各种各样的,你能想象到的所有的应用程序。
210 |
211 | 作为本节的结束,我们看看清单 6-1 中的文档用 HTML5 规范书写是什么样子的。见清单 6-2。而有关 CSS 和 JavaScript 相关的内容,将在本书第五篇“信息的计算机展现”和第六篇“计算机编程语言”中讲述。
212 |
213 | 清单 6-2 一个简单网页的 HTML5 版本
214 |
215 | ```
216 |
217 |
218 |
219 |
220 |
221 | Hello, World!
222 |
223 |
224 |
225 |
226 |
227 | HELLO, WORLD!
228 |
229 |
230 |
231 | This is the first Web page in HTML5.
232 | Click HERE to see the second Web page.
233 |
234 |
235 |
236 |
237 | ```
238 |
239 |
240 | ## 6.4 XML
241 |
242 | XML(eXtensible Markup Language,可扩展标记语言)也是由 W3C 颁布、维护和发展的标准。XML 最早于 1998 年 2 月成为 W3C 的推荐标准。和 HTML 类似,XML 也可看成是 SGML 的子集;但和 HTML 不同的是,XML 主要用来表述复杂的结构化数据,比如某个行政单位的组织机构或者人员清单以及人员特性,如年龄、性别、学历等等。另外,HTML 由浏览器解析并展现,而 XML 的处理更为灵活,任何程序都可以利用 XML 来存储、传输自己的结构化数据。
243 |
244 | XML 和 HTML 有如下不同:
245 |
246 | - XML 不是用来替代 HTML 的,但可以看成是 HTML 的补充,主要用来存储和传输结构化数据,其表述方式和特定的软件或硬件系统无关。
247 | - XML 的标签不是预先定义好的,而需要由用户自行定义。
248 |
249 | 除此之外,XML 具有严格的语法,HTML 则更加灵活。比如清单 6-1 中,我们可以省去两个 标签,浏览器仍然能够正常处理并展示其内容,但 XML 却不行。
250 |
251 | 由于 XML 的灵活性,加上是 W3C 的推荐国际标准,因此,XML被广泛应用于不同的场合,出现在各种各样的软件中。
252 |
253 | ### 6.4.1 XML 示例
254 |
255 | 清单 6-3 给出了一个典型的 XML 文件,用来定义一个数据库表的字段。
256 |
257 | 清单 6-3 一个定义数据库表格的 XML 文件
258 |
259 | ```
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | ```
278 |
279 |
280 | 和 HTML 类似,XML 使用标签(tag)来定义一个元素(也称为节点或对象),通过标签的层级关系来定义元素之间的结构,另外,标签中可以定义一个或者多个属性(attribute)。
281 |
282 | 清单 6-3 给出的 XML 文件中,第一行相当于是 XML 文件的文档类型签名,以方便程序或开发者识别这是一个 XML 文件,其中包含了 XML 标准版本号以及该文件的字符编码方式(通常就是 UTF-8)。第二行定义了一个 schema 标签,这是这个 XML 文件的根元素,类似 HTML 文件中的 html 标签;每个 XML 文件只能包含一个根元素。随后,该文件使用 table 标签定义了一个数据库表格,其名称为 foo\_bar(使用 table标签的 name 属性定义),在 table 标签内,使用 field 标签定义了三个字段,以表明字段是表格的子元素。类似地,通过 field标签的属性定义了三个字段的名称、类型等特性[^7]:
283 |
284 | - 字段的名称使用 field 标签的 name 属性定义。
285 | - 字段的数据类型使用 filed 标签的 type 属性定义,其值为 I 表示是整数类型,其值为 C 表示是字符类型。当类型为 C 时,使用 size 属性定义其长度。比如上述示例定义的 id 字段是整数类型,foo 字段也是整数类型,但 bar 字段是字符类型,长度为 32。
286 |
287 | 需要注意的是,上述示例 XML 文件中,filed 标签中还包含有 key、unsigned、notnull、default 等子标签。理论上讲,这些子标签仍然定义的是数据库字段的特性,分别对应主键、无符号整数、非空、默认值等。但在上面的例子中,使用了子标签来定义这些数据库字段的特性。但我们也可以使用下面这种方式来定义 foo 这个数据库字段:
288 |
289 | ```
290 |
291 | foo
292 |
293 |
294 | ```
295 |
296 |
297 | 因此,除了上面所说标签是由开发者自行定义的之外,使用 XML 来定义结构化数据时,我们可以自行定义数据的结构化层级关系以及元素属性。比如定义一个字段时,我们可以将名称、类型、主键、无符号、默认值等特性作为字段的子元素来定义,也可以使用标签属性来定义。当然,结构化数据的层级关系往往是比较清晰的,但就特定的元素特性来讲,哪些适合定义为元素属性,哪些适合定义为子元素,则没有统一的准则,大部分情况下由特定 XML 文件格式的开发者自行确定。
298 |
299 | 比如上面给出的定义 foo 这个数据库字段的两种方式中,我们始终使用标签属性 type来定义数据库字段的类型,这是因为数据库字段的类型是固定的,而不是任意值,通过标签属性定义类型,可帮助我们使用 XML 的严格语法检查机制来查验文档的合法性。这涉及到 DTD(Document Type Definition,文档类型定义)的概念。
300 |
301 | ### 6.4.2 DTD
302 |
303 | 如果要使用 DTD 来验证一个 XML 文档的合法性,则需要在 XML 文档的头部增加 DOCTYPE 定义。本质上,DTD 定义了一个 XML 文档如下几个方面的规则:
304 |
305 | - 根元素名称。在我们的数据库表格示例中,根元素为 schema。
306 | - 元素之间的嵌套规则,比如一个元素可以包含什么样的子元素、哪些子元素是可选的等等。以清单 6-2 为例,根元素 schema 中可包含一个或多个 table 元素,table 元素中要至少包含一个 field 元素,而 filed 元素中可包含多个可选子元素,如 notnull、key 等等。
307 | - 有效的元素属性、属性默认值、属性的值类型(如字符串、枚举量等)。以清单 6-2 为例,filed 元素的属性有 name、type、size 等:name 属性是必须定义的,类型为字符串;type 属性是必须定义的,类型为枚举量;size 属性不是必须的,类型为字符串。
308 |
309 | DTD 的定义相对来讲比较复杂,比如就清单 6-3 所示的 XML 文档,对应的 DTD 大致如清单 6-4 所示。
310 |
311 | 清单 6-4 数据库表格的 XML 描述文档对应的文档类型描述
312 |
313 | ```
314 |
315 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
330 |
331 | ]>
332 |
333 | ...
334 |
335 | ```
336 |
337 | 清单 6-4 中,大写的单词是 DTD 的保留字,读者根据保留字的英文含义,大致就可以看明白 DTD 的定义方法。需要注意的是,清单 6-4 仅仅给出了针对清单 6-3 示例 XML 文档的最小文档类型定义;一个完整用于描述数据库表格格式的 XML 文档之 DTD,要比清单 6-4 复杂很多。清单 6-3 这个示例其实来自于 ADOdb XML Schema,其完整描述可见网页:[http://phplens.com/lens/adodb/docs-datadict.htm#xmlschema](http://phplens.com/lens/adodb/docs-datadict.htm#xmlschema),对应的 DTD 见清单 6-5。
338 |
339 | 清单 6-5 ADOdb XML Schema 对应的 DTD
340 |
341 | ```
342 |
343 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
368 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
387 |
388 |
389 |
395 | ] >
396 | ```
397 |
398 |
399 | 另外,我们也可以在 XML 文档中引用单独存放的 DTD 文件,而无需在 XML 文档中包含完整的 DTD 定义。比如针对上面的示例,我们可以将清单 6-4 中的 DTD 保存为 adodb\_xml\_schema.dtd 文件,并保存在互联网上,然后使用下面的方式访问这个 DTD 文件:
400 |
401 | ```
402 |
403 |
404 | ```
405 |
406 |
407 | 限于篇幅,本书不打算详细讲述 DTD 的细节,有兴趣的读者可自行参阅相关书籍或技术文档。
408 |
409 | ### 6.4.3 XML 的应用
410 |
411 | 由于 XML 可用来定义复杂的结构化数据,且具有较好的人机共读特性,因此,XML 得到了广泛应用。比如上面的示例使用 XML 文档描述数据库结构,而在 Android 系统中,XML 被用来描述图形用户界面,也被用来定义本地化字符串。但使用 XML 最多的场合则是 Web 服务。所谓 Web 服务,也是通过 HTTP 协议提供的,只是其返回的内容格式不是通常的 HTML,而是 XML;HTML 给浏览器使用,最终为普通用户渲染出图文并茂的网页,XML 则是提供给另外一个计算机程序使用的,由这个程序做进一步的处理。也就是说,我们可以这样理解 XML 的出现:XML 是为了将服务器可以提供的数据通过万维网传输给其他计算机(或程序)使用而发明的。知道了这一点,我们也就很容易理解为什么 XML 的语法要比 HTML 严格很多,当然也更加灵活。
412 |
413 | 因此,在 Web 服务大行其道的今天,大部分 Web 服务提供的结果是通过 XML 来描述的。然而,随着 JSON 的流行,一些新开发出来的 Web 服务,不再使用 XML 来描述结果,而使用JSON 描述结果。
414 |
415 | ## 6.5 JSON
416 |
417 | 和 XML 类似,JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式。但 JSON 明显比 XML 具有更好的特性:易于人阅读和编写(JSON 不使用尖括号和标签),同时也易于计算机解析和生成。从 JSON 的名称可知,这种结构化数据的表示法来源于 JavaScript 编程语言,也就是 Web 应用的唯一编程语言,这个编程语言以易学易用著称。
418 |
419 | JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族(包括C、C++、C\#、Java、JavaScript、Perl、Python 等)的习惯。这些特性使得 JSON 成为理想的数据交换格式。JSON 的构造基于如下两种基本形式:
420 |
421 | - 名称/值对的集合。在不同的编程语言中,“名称/值对”被理解为对象、记录、结构、字典、哈希表、键值对或者关联数组。
422 | - 有序的值列表。即大部分编程语言所指的数组。
423 |
424 | 这些都是各种计算机编程语言中常见的数据结构。事实上大部分现代计算机编程语言都以某种形式支持这些数据结构。因此,JSON 描述的数据在同样基于这些结构的编程语言之间交换时非常方便。
425 |
426 | 清单 6-6 给出了一个用 JSON 描述结构化数据的示例。这个示例用 JSON 描述了清单 6-3 中使用 XML 描述的数据库表 foo\_bar 的结构。
427 |
428 | 清单 6-6 用 JSON 描述数据库表的结构
429 |
430 | ```
431 | {
432 | name: "foo_bar",
433 | fields: [
434 | {name: "id", type: "I", key: "yes", unsigned: "yes"},
435 | {name: "foo", type: "I", notnull: "yes", default: 0},
436 | {name: "bar", type: "C", size: 32, notnull: "yes", default: ""}
437 | ]
438 | }
439 | ```
440 |
441 |
442 | 对比清单 6-3 和清单 6-6,我们可以看到使用 JSON 描述的数据库表格的结构更加清晰。一个 JSON 对应于一个对象,一个对象可以有多个属性,每个属性有其名称和值。而属性的值可以是字符串、数值或者数组,也可以是另外一个对象,除此之外,还可以有 true(真)、false(假)、null(空) 等三个特殊值。比如我们定义 foo\_bar 这个数据库表格时,对应的 JSON 对象中包含两个属性,用花括号({})包围:一个是 name,用来表示表格的名称,其值为字符串“foo\_bar”;一个是 fields,用来定义表格的字段,其值为数组,用中括号([])包围。
443 |
444 | fields 包含三个字段,每个字段又是一个 JSON 对象,每个对象用花括号({})包围,中间用逗号(,)分隔。这些描述字段的对象有 name、type、key、unsigned、notnull、default 等属性,分别使用字符串、数值等作为相应属性的值。
445 |
446 | 需要注意的是,清单 6-6 在定义不同的字段时,这些字段对象的属性并不完全一样。在 JavaScript 中,未定义的属性对应的值是 undefined,这个值和空值(NULL)不同,而在其他编程语言中,可能无法区分这两种情况。因此,程序在解析上述 JSON 数据时,需要注意这个区别。当然,我们也可以强制所有的字段对象都具有相同的属性,并对这些额外的属性取默认值,如清单 6-7 所示。
447 |
448 | 清单 6-7 用 JSON 描述数据库表的结构(增强版)
449 |
450 | ```
451 | {
452 | name: "foo_bar",
453 | fields: [
454 | {name: "id", type: "I", size: null, key: true, unsigned: true,
455 | notnull: true, default: 0},
456 | {name: "foo", type: "I", size: null, key: false, unsigned: false,
457 | notnull: true, default: 0},
458 | {name: "bar", type: "C", size: 32, key: false, unsigned: null,
459 | notnull: true, default: ""}
460 | ]
461 | }
462 | ```
463 |
464 | 清单 6-8 给出了一个真实的 Web 服务接口返回的 JSON 数据示例。这个 Web 服务接口返回指定国家的编码、英文名称、标准简写以及已知区域的本地化名称。需要特别说明的是 localizaed\_name 对应的字符串值使用了 \u 作为前缀,这指 \u 之后定义了一个 UNICODE 字符,用四个十六进制字符表示,是 UNICODE 字符的内码值。
465 |
466 | 清单 6-8 用 JSON 描述阿富汗的国家码以及不同区域下的名称
467 |
468 | ```
469 | {
470 | "status": "ok",
471 | "endpoint": "/list/countries/item_detail/{token}/{numeric_code}",
472 | "items": {
473 | "numeric_code": "4",
474 | "name": "Afghanistan",
475 | "alpha_2_code": "AF",
476 | "alpha_3_code": "AFG"
477 | },
478 | "extras": [
479 | {
480 | "locale": "en",
481 | "localized_name": "Afghanistan"
482 | },
483 | {
484 | "locale": "zh",
485 | "localized_name": "\u963f\u5bcc\u6c57"
486 | },
487 | {
488 | "locale": "zh_CN",
489 | "localized_name": "\u963f\u5bcc\u6c57"
490 | },
491 | {
492 | "locale": "zh_HK",
493 | "localized_name": "\u963f\u5bcc\u6c57"
494 | },
495 | {
496 | "locale": "zh_TW",
497 | "localized_name": "\u963f\u5bcc\u6c57"
498 | }
499 | ],
500 | "elapsed_time": "0.0088",
501 | "memory_usag": "0.65MB"
502 | }
503 | ```
504 |
505 |
506 | 如前所述,由于使用 JSON 描述结构化数据时简洁的表达方式、便于各种各样系统的处理、便于人机共读的这些特点,JSON 已有取代 XML 的趋势,很多新系统已经开始仅提供 JSON 格式的数据而不再提供 XML 格式的数据了。
507 |
508 |
509 |
510 | [^1]\: 有关文字输出的双向排版问题,将在本书第五篇“信息的计算机展现”中讲述。
511 |
512 | [^2]: 亦定义有针对不同处理类型的环境变量 LC\_COLLATE(字符串排序规则,用于正则表达式)、LC\_CTYPE(字符类型,用于正则表达式)、LC\_MESSAGES(本地化字符串)、LC\_MONETARY(用于货币格式化)、LC\_NUMERIC(用于数字格式化)、LC\_TIME(用于时间和日期的格式化)。
513 |
514 | [^3]: 亦简写为 i18n,其中的 18 表示 I 和 n 之间有 18 个字母。
515 |
516 | [^4]: UTC 是 Universal Time Coordinated(协调世界时)的简写,即格林威治时间。
517 |
518 | [^5]: 需要注意的是,从操作系统软件栈功能的划分角度讲,时区并不是由操作系统内核维护的,而是由标准函数库维护的。由于 gettimeofday/settimeofday 这两个接口涉及到了时区的获取和设置功能,因此在后来的标准修订中不再推荐使用这两个接口,转而推荐使用 clock\_gettime 等接口。
519 |
520 | [^6]: 感兴趣的读者可查阅 W3C 官方网站:http://www.w3.org。
521 |
522 | [^7]: 在这个小节,请注意特性(Property)和属性(Attribute)这两个术语的使用场合。前者泛指元素或对象的性质、特性,如人员年龄、性别,后者特指 XML 的标签属性。
523 |
--------------------------------------------------------------------------------
/textbook/part-1-chapter-7.md:
--------------------------------------------------------------------------------
1 | # 第 7 章 结语:标准的产生和演进
2 |
3 | 在本篇中,我们讲述了计算机如何表述各种各样的信息类型,而这些表述方式,大部分会成为标准或者规范,以方便不同的计算机系统之间交换数据。本章作为本篇的总结,将探讨标准的产生和演进中的一些规律性主题。
4 |
5 | ## 7.1 标准的产生和制定
6 |
7 | 本篇我们了解了浮点数、UNICODE、JPEG/MPEG/H.26x、HTML/XML 等标准。这些标准最终由国际化的标准化组织通过各种各样的工作小组形成草案,经过讨论和修订而变成最终的标准。这些标准化组织往往是国际化的非营利性组织,比如 IEEE(电气和电子工程师协会)、ISO(国际标准化组织)、ITU(国际电信联盟)、W3C 等。
8 |
9 | 一般而言,一个标准形成之前,相应的技术都会有一个实现雏形。通过局部市场的检验,可证明相关技术的确是有其市场需求的,这时,将相应的技术进行标准化的工作就会提上标准化组织的日程。由于标准的制定者往往是国际顶尖的专家,故而最终的标准会超越当初的实现雏形。通过标准化组织对某项标准的推行,产业界会形成一个针对该标准的实施合力,相应的实施会极大促进产业的发展:大家分工协作,各赚各的钱。
10 | 这一规律在音视频编码技术的标准演进过程中表现得淋漓尽致。最初,影视界、信息技术界(微软、苹果、Adobe 为代表)的厂商或标准化组织为各自的产业制定有相应的标准(NTSC/PAL、AVI 等),随着技术的融合,MPEG 出现了,之后产业界形成了良好的配合关系。有厂商收取相关技术的专利费,其他厂商设计和生产音视频编解码器,而软件厂商则整合这些技术为消费者所用。
11 |
12 | 那些不符合以上规律而产生的标准,就可能会陷入不被市场买账的窘境。就像 XHTML 这个由 W3C 闭门造车开发出来的标准一样。
13 |
14 | 和计算机软件技术相关的标准的形成中,还有一个重要的玩家,即开源社区。由于某些原因,某些标准(或者事实上的工业标准),会存在专利或者高科技禁运限制。开源组织的存在,则会通过公益事业的方式来制定一些标准来对抗此类标准。如自由软件基金会制定 PNG 格式来对抗 GIF 专利,Xiph.Org 基金会通过发展免费、开源的音视频编码技术来对抗某些专有的音视频编码技术等等。
15 |
16 | 在标准的演进中,针对同一技术的不同标准之间的竞争也会给技术和产业的发展带来良好的促进作用。比如音视频编码技术中 MPEG 和 X.26x 系列标准之间的竞争关系。这种竞争关系此消彼长,有时候会通过互相借鉴而促进彼此发展。但有时候会很残酷,身处其中的厂商一旦选择了一种日后会被市场抛弃的标准,对该厂商来讲将是致命的。比如 HD DVD 和蓝光光盘(BD,Blue-ray Disc)之间的竞争,最后以蓝光光盘取胜而结束。再比如,比如移动视频广播(手机电视)领域,国内曾出现过 CMMB 和 TMMB 两个标准,最终 CMMB 获胜。不过遗憾的是,随着智能手机的普及和无线通信带宽的提高,手机电视最终失去了市场地位。
17 |
18 | ## 7.2 技术标准的演进规律
19 |
20 | 工业界的标准,往往会对某个领域的发展带来很大的促进作用,在计算机领域也不例外。比如 MPEG 系列标准的产生,带来了影音产业的革命性发展,产生了 VCD、DVD、MP3 播放器、MP4 播放器等等数码产品,将卡式录音机、磁带录像机埋进了坟墓,同时也带动了模拟电视系统向数字电视系统的演进。
21 |
22 | 在标准的演进过程中,每一次的技术进步几乎会重建整个工业界,一方面会给很多企业带来新的发展机遇,也会让很多企业快速丧失原本拥有的优势。比如苹果公司,在本世纪初瞄准了在线音乐市场,制造了时尚的 iPod 产品,并进而在 2007 年重新定义了智能手机,iPhone 随之大卖,最终将苹果公司推向了全球市值最高的上市公司行列。反例则是中国众多的 VCD、DVD 播放机制造企业。在 VCD 取代磁带录像机的那几年(上个世纪90年代晚期),造就了以“爱多”为代表的 VCD 播放机制造产业,但在随后的 DVD 时代,“爱多”等播放机企业纷纷倒下,未能赶上时代的发展。
23 |
24 | 标准的更迭速度往往在相关标准产生的前几年比较高。比如 MPEG-1 到 MPEG-2 标准,中间只相隔两年时间,而从 MPEG-2 到 MPEG-4 的演进,中间经过了四年时间。相同的情况也发生在 HTML 标准的演进过程中,从 HTML 2.0 到 HTML 3.2 仅用了两年时间,而从 HTML 4.01 到 HTML5,则花费了十五年时间。
25 |
26 | 这种现象是可以理解的,毕竟在标准刚出现之时,可能尚不成熟,版本的迭代速度自然会比较快,而随着标准变得更加成熟,向下个版本的演进时间会随之加长。这种现象值得产业界深思。对一个企业来讲,紧盯标准的发展动向,按照标准的演进规律及时更新产品是非常必要的,若不思进取,可能会像“爱多”等品牌一样,随着新技术的普及而迅速倒下。
27 |
28 | 另外,标准的产生往往滞后于市场需求。比如在文字编码标准的演进过程中,在 UNICODE 还没有成为国际标准,且没有得到广泛认同的情况下,相关企业或者国家会自行制定自己的文字编码标准。最典型的当属 GB2312 字符集,由于 GB2312 字符集定义的码位太少,无法适应互联网、字处理、出版等行业的市场需求,Windows 操作系统的开发者微软为了在中国大陆推广自己产品,不得不自行扩充 GB2312 字符集,形成了 GBK 字符集的雏形。类似的情况也发生在 HTML 标准的演进过程中,在 HTML5 标准尚未正式颁布之前,很多浏览器的开发参照标准的草案进行,或者按照对厂商有利的方式自行演进。比如微软在 IE 中引入的 ActiveX 控件,Adobe 的 Flash 插件等等。但这些技术在实际产品中的应用,最终也会影响标准的形成。比如 Adobe Flash 插件间接导致了 HTML5 中多媒体标签以及 Web Socket 等的引入。
29 |
30 | 某些标准会因为无法得到产业界的认可而最终丧失其地位,典型的如 HTML 演进过程中产生的 XHTML。还有杂七杂八的图像格式,如 PCX、TIFF 等等(尽管这些图像格式并不是如 UNICODE 那样的国际标准,但也是某个平台或者特定软件或系统环境中的标准)。另外如中国政府定义的 GB2312 字符集标准,很快被 GBK 以及 GB18030、UNICODE 替代。这些容易丧失地位的标准,往往具有如下的特征:
31 |
32 | - 某个软件厂商为自己的软件系统制定的标准,如 Adobe 的 Flash,微软的 AVI、ASF 等视频编码及格式。这些标准往往为封闭的市场开发,局限性较多,容易被开放的、跨平台的标准或技术取代。
33 |
34 | - 区域性组织(如政府)制定的标准,如各国各地区政府制定的字符集标准。这些标准在相关技术的发展和演进过程中占有一定的历史地位,但最终会被国际性的标准替代。
35 |
36 | - 标准制定者无视市场状况一厢情愿制定的标准,典型的如 XHTML。
37 |
38 | 当然,随着技术的发展,某些标准会丧失对应的软硬件载体或者市场需求,从而逐步淡出历史,比如 MPEG-1。虽然 MPEG-1 Layer 3(MP3)音频编码技术的生命力要强一些,但相信最终会被 AAC 替代。
39 |
40 | ## 7.3 可扩展性及兼容性设计
41 |
42 | 如前所述,每个标准都要经历一个从不太成熟走向成熟的演进过程。因此,在新的标准成型之前,必须考虑的一个重要问题就是向前的兼容性设计。比如字符集的设计,所有其后的字符集及编码技术,都要兼容 ASCII,而 GB18030 字符集也要兼容 GB2312 字符集。为了实现向前的兼容性,任何标准在设计时都要做可扩展性考虑。
43 |
44 | 比如 ASCII 字符集,假如在一开始时就将一个字节的全部八位都用光,定义 256 个字符,那其后的所有字符集及其编码都没法和 ASCII 兼容。同样的设计也体现在 UNICODE 字符集的演进过程中;而 UNICODE 的 UTF-8 编码设计,则完美展现了可扩展性及兼容性之间的平衡艺术。
45 |
46 | 类似地,MPEG-1 到 MPEG-2 的演进也充分考虑了兼容性,DVD 播放机可以支持 VCD 盘片的播放,而不需要做额外的设计,比如专门为 VCD 盘片设计一个独立的盘片仓。
47 |
48 | 当然,随着技术的发展,兼容性可能会带来巨大的历史包袱,使得相关的技术变得复杂而笨重。这时,就需要打破兼容性。打破兼容性的代价是巨大的,这需要标准制定者做出取舍。如果标准制定者不能顺应历史潮流,则可能会被其他标准替代。
49 |
50 | 不过,一般情况下,兼容性要比先进性重要得多。比如 W3C 的 XHTML 最终没有战胜市场需要的兼容性。而只有在新的市场出现时,新的标准才可能有机会全面替代不兼容的老标准。比如移动互联网的出现,让 H.264 替代 MPEG 成为事实上的移动互联网视频编码标准。那么正在快速普及的 4K 超高清视频系统,会不会让 X.265 标准独步天下呢?这还待市场检验。
51 |
52 | ## 7.4 提升综合实力,积极参与国际标准的制定
53 |
54 | 由于历史原因,我国在计算机和通信领域的技术基础相当薄弱,我们很少有企业有资格参与到上面提到的这些标准制定当中。
55 |
56 | 尽管大部分制定国际化标准的组织是非营利性的,但这些组织的人员却主要来自于企业,尤其是欧美日的跨国公司,如微软、苹果、IBM、英特尔、爱立信、索尼等。既然参与标准制定的专家背后是企业,自然就会有利益之争。可想而知,标准的制定过程其实是划定相关企业在市场上的势力范围的过程,这和当年列强瓜分中国的情形是差不多的。具体而言,就是很多标准中包含有很多专利技术,而这些专利技术是由企业拥有的。显然,一旦某些专利技术被纳入标准之中,那对应的企业就可以坐收专利许可费了。
57 |
58 | 因此,参与到国际化标准的制定过程中,对企业,对国家都是有利的。但问题是,一个国家或者企业的实力不够时,将很难获得相应的资格来参与到标准的制定当中,毕竟游戏规则是人家定的。
59 |
60 | 在实力达不到的情况下,政府会通过一些政治或者行政手段来保护自己的企业或产业。比如,通过自己的市场地位来换取一定的资格。这一点在无线通信领域的 3G 标准竞争中表现非常明显。TD-SCDMA 是由我们国家主导发展的 3G 标准,在技术和产业化水平上,其成熟度和 WCDMA、CDMA2000 等由欧美国家提出的标准相差很远。但将 TD-SCDMA 变成一项国际化标准,则可以打破无线通信领域的标准由欧美企业长期把持的现状,从而有利于我们国家未来在无线通信领域的话语权。处于此项考虑,经过艰难和长期的博弈,TD-SCDMA 成了国际任何的 3G 标准之一,而中国政府同意针对其他两个标准放开频段资源,由相关企业自主选择 3G 标准。这明显是一种利益的交换和平衡。
61 |
62 | 尽管后来由中国移动发展的 TD-SCDMA 3G 网络效果并不好,但自此后中国企业在无线通信领域的标准话语权得到了很大的提升。在 4G 无线通信标准中,中国相关企业拥有了越来越多的核心专利,而在面向未来的 5G 无线通信标准的研发中,据悉中国的华为公司占据了非常强势的地位。相同的故事也发生在视频编码技术领域,在 X.265 视频编码技术中,华为公司也占据了主导地位。
63 |
64 | 与此同时,中国的互联网相关企业,如阿里巴巴、腾讯、百度等,也开始重视标准的重要性,加入了 W3C 等组织当中。
65 |
66 |
--------------------------------------------------------------------------------
/textbook/preface.md:
--------------------------------------------------------------------------------
1 | @SUBJECT [计算机软件技术导论](toc.md)
2 | @AUTHOR [魏永明](https://github.com/VincentWei)
3 | @DATE 2015-xx-xx
4 | @LANG 简体中文
5 | @COPYING 版权所有 © 2022 魏永明
6 | @LICENSE 保留所有权利
7 |
8 | # 前言
9 |
10 | ## 计算机是何物?
11 |
12 | 我们通常意义上所讲的“计算机”,其全名应为“数字计算机[^1]”,或者“电子计算机”。“电子计算机”这个名称强调当前计算机的构造方法,即通过电子的晶体管来构建计算机。这不同于通过机械的方法,或者前沿的量子的方法来构建计算机。和电子计算机这个名称相比,“数字计算机”则可以更加一般性地定义计算机,这个名称强调如下事实:
13 |
14 | 计算机本质上处理的是数,而且是离散的数字。通过有限的离散的数,我们可以运用代数运算来近似地描述连续的数学公式,从而帮助人们进行运算。通过适当的编码,我们可以使用这些离散的数来表示其他现实世界中的东西,比如文字、声音、图像等等。而这些统统可被称为“信息[^2]”。
15 |
16 | 从理论讲,不论我们如何构造计算机,最终我们只能从离散的数字角度来模拟真实的世界。比如,我们无法用包括计算机在内的任何仪器来完整描述一个无理数,尽管理论上讲无理数的数量远远大于有理数,更不用说整数或者自然数了。因此,不管是电子的、机械的(如算盘或者巴贝奇[^3]在十九世纪计划用蒸汽机及齿轮构建的分析机)还是量子的计算机,最终我们只能有限地描述世界。但这已经足够,因为从哲学上讲,真实的世界其实也不是模拟的,因为数学上的不确定性以及量子角度的不确定性都说明了这个现实:我们其实生活在离散的宇宙中,而不是连续的、模拟的宇宙中,即使是时间,本质上也是离散的[^4]。
17 |
18 | 因此,把握上述这个本质,我们就可以正确认识计算机——计算机的本质特征可以概括为如下两点:
19 |
20 | - 第一, 离散的数字。因为数学上的不确定性以及物理上的不确定性,期望一台机器可以表述连续的数学世界是不现实也是不可能的。因此,不论是什么样的仪器,其最终可处理的东西只能是离散的数字,计算机也不例外。计算机的计算能力以及容量都取决于计算机可以方便地表述多少个离散的数字。我们日常所说的 8 位、16 位或者 32 位、64 位计算机,是从信息最基础的度量单位“位(Bit)”的角度讲的;8位的计算机总共有 28(256)个离散的数字,而 64 位的计算机,则总共有 264(18446744073709551616)个离散的数字。后者的容量或者计算能力显然要比前者强很多倍。
21 | - 第二, 编码。通过编码,我们可以用离散的数字表述现实世界中的各种各样的东西,包括数据、文字、声音、图像等等。比如,我正在使用笔记本电脑敲进去的这本书,所使用过的汉字(包括英文字母、数字、标点等,可统称为字符)大致不会超过 5000 个,我们为每个字符编一个号,就可以用一个数字序列来表示这本书的内容。实质上,计算机内部也是这么做的,当然情况要复杂一些。编码还有一个好处就是,我们可以针对这些数字做各种各样的数学运算或者操作,比如可以统计这个数字序列的长度,再乘以0.01,结果差不多就是我可以通过出版这本书拿到的每本书的版权收益。这个过程本质上也是编码,因为我们可以将统计字数和计算版税的动作也编个号,建立它们之间的一个映射关系。这种包括运算和逻辑在内的编码,也可以称为“算法”。
22 |
23 | 上面这两个计算机的本质特征,对所有类型的计算机都是适用的。比如对人和算盘形成的“计算机”而言,显然其只能表述有限的数字,其容量取决于算盘的长度(或有多少个珠子),其编码就是算盘的数字表达规则(上面的珠子一个表示 5,下面的珠子一个表示 1)以及操作人掌握的口诀及计算公式。就其他形式的计算机来讲,不论电子的计算机在多大程度上或以多快的速度被量子的计算机取代,上面这两个本质特征都不会发生变化。
24 |
25 | 更进一步,我们可以将物理的计算机本身称为“硬件”,而编码以及编码的方法称为“软件”。如此一来,硬件是变化的(从机械到电子再到量子或者将来的任何可能形态),但软件则是“永恒”的(上面两个本质不会因为硬件的变化而变化)。另外要强调的是,我们通常所讲的“软件”,的确更多地和上述计算机的本质特征中的第二点(编码),发生更直接的关联。
26 |
27 | 于是,我们可以得出一个结论:软件才是计算机科学的灵魂,硬件只是一种载体、一种媒介。即使我们没有硬件,也可以编码(编程),就像艾达[^5]为巴贝特的分析机编程一样(分析机从来没有建成过,但艾达这位天才女性却为之编写了程序),或者数学家为仅在思维中存在的图灵机[^6]编程一样。
28 |
29 | 本书将从信息的计算机处理(表述、存储、计算、传输、展现等)这一角度来为读者讲述计算机软件的本质及方法。这从某种程度上可称之为“计算机软件方法论”。
30 |
31 | ## 计算机软件
32 |
33 | 如上所述,计算机软件实质上就是对各种计算机的编码方法及规则的统称。
34 |
35 | 笔者最初建立的“计算机软件”概念,是将一个程序从一张五英寸的软磁盘上复制到 IBM 8088 PC 机上然后通过 DOS 操作系统执行该程序。你会听到一段简陋的音乐,或者进入一个游戏场景,然后通过上下左右键等按键来控制游戏中的人或者枪。那时,笔者将这种称之为“程序”的东西视作“计算机软件”。但随着对计算机了解的深入,笔者发现,对计算机而言,软件就是除了硬件之外的所有东西,没有软件,PC 机也好,服务器也好,甚至我们现在日常使用的智能手机都将失去作用——这些机器什么都做不了。
36 |
37 | 本质上讲,计算机软件是指可在计算机中执行的程序集合。“程序”又可以看成是之前所说的某种编码,尤其指包含有计算和处理逻辑的编码序列。说白了,就是一堆数据,但数据是有序的。
38 | 神奇之处在于,这堆数据通过在计算机中执行,就可以完成很多事情,简单的如求平方根,复杂的如输出乐曲、编辑和打印文本等,而这一切由前述的编码以及编码的规则所定义。计算机则忠实执行给定的指令或编码序列,从而完成我们期望的任务。
39 |
40 | 运行在现代意义上的计算机之软件,根据其功能的不同,可以划分一种分层的堆栈结构。最底下的软件通常称为“固件(firmware)”,用于计算机的启动及自检(检查计算机的外设是否工作正常并执行初始化),其上的那层软件称为“操作系统(operating system)”,再往上的软件称为“应用程序(application)”。读到这里,读者应该可以明白本书引言中提到的“全栈工程师”这个名称的来由及其比较具体的含义。
41 |
42 | 图 1 通用操作系统的软件栈
43 |
44 | 在智能手机、平板电脑等针对个人的计算机设备上,为方便程序开发或者做系统保护,人们又设计了一层新的栈,这一层软件运行在传统的操作系统之上,被称为“框架(framework)”,而应用程序又被简称为“应用(app)”。框架对应用程序来讲,其实是一种限制,它限制了应用程序可以做什么,不能做什么。比如在 Android或者 iOS 系统上,一个应用不能随意访问所有的系统资源,以此来保护设备的基本功能不被病毒或者恶意程序所破坏,或者保护用户的隐私不被轻易泄露[^7]。这大概是应用(app)这个词从应用程序(application)这个词被截断而创造出来的原因。
45 |
46 | 图 2 智能操作系统的软件栈
47 |
48 | 在更为简单的计算机系统中,比如智能手机出现之前的功能手机(feature phone)或者一些简单的智能化电子设备(如电子词典)中,软件的栈式结构可能没有那么清晰,毕竟这种设备可以完成的功能是有限的。但开发人员会在此类简单的计算机系统中有意或无意地使用类似的栈式分层概念,以使得自己的软件结构变得清晰和可维护。
49 |
50 | 那么,为什么计算机软件要使用这种分层设计的方法呢?这要从计算机软件的演进历史谈起。
51 | 计算机软件的演进
52 |
53 | 现代意义上的电子计算机出现在上个世纪二战期间(1940年代)。那时,人们通过在纸带上打眼的方式表示数据和指令(即编码),然后计算机读取纸带完成计算。我们可以将纸带上的编码序列看成是对应的计算机软件,或程序。那时及其后很长一段时间,人们编写的计算机程序中全是计算机可以直接识别的指令及其要运算的数据,而且是二进制的[^8]。此时的程序如果写在纸上,大概是这个样子的:
54 |
55 | ```
56 | 00000001
57 | 00000000
58 | 00000010
59 | 00000001
60 | ```
61 |
62 | 上面这段程序是我虚构的,大致是说将0作为当前操作的数字(指令00000001),将1加到当前操作数的值上并将结果保存起来(指令00000010)。
63 |
64 | 二进制表示的数据(比如75,其二进制表述为01001011)显然不易识别,且容易出现差错。聪明的人立即可以想到,既然计算机的处理能力那么快,为什么不让它做更多的工作?比如我们可使用更加易读的数字表示方式,如十六进制来表示数字(75 用十六进制表示就是 4B),然后让计算机自己将该十六进制的数字变成二进制并执行不就可以了吗?如此以来,程序将更为简洁,且易读易维护。于是,我们可以将上面的程序写成下面这个样子:
65 |
66 | ```
67 | 01 00
68 | 02 10
69 | ```
70 |
71 | 如果我们将用于表示指令的编码使用更加易读的文字短语来表示,那这个程序可以更进一步写成:
72 |
73 | ```
74 | SET 0
75 | ADD 1
76 | ```
77 |
78 | 上面这段程序使用了更加复杂的编码映射关系,引入了来自人类语言的单词(SET、ADD)来表示计算机要完成的工作,因此,这种程序被称为“汇编”。因为使用了来自人类语言的单词或类似的语法,又称为“计算机编程语言”。汇编语言的诞生极大提高了编写计算机程序的效率,而最大的好处就是,我们可以借此编写更加复杂的程序。
79 |
80 | 另外,善于抽象和总结的人们又进一步意识到,我们还可以用更加接近人类的方式编写计算机程序,从而更容易地完成编程任务和表达复杂算法。于是,FORTRAN 等高级的编程语言被设计出来。这种符合特定规则(词法和语法)的编程语言被称为“高级编程语言”。比如上面的程序,可以使用高级编程语言写成:
81 |
82 | ```
83 | a = 0
84 | a = a +1
85 | ```
86 |
87 | 通过使用高级编程语言,人们可以使用类似人类语言的方式为计算机编写程序。当然,这种方法编写的程序,是不能直接被计算机所识别并执行的,而需要经过解释或者编译。
88 |
89 | 解释的过程就是将使用编程语言编写的程序直接读取并按照相应的规则翻译成计算机课识别的指令。汇编语言最为简单,只要建立所使用单词和给定计算机指令代码之间的映射关系即可,几乎不涉及语法规则问题,而其他高级语言的解释显然更为复杂。
90 |
91 | 编译的过程则将高级编程语言编写的程序先整个进行翻译并最终生成可被计算机直接运行的编码序列,然后将该编码序列交给计算机执行。
92 |
93 | 从执行效率上讲,对同一种语言而言,显然编译然后执行要比边解释边执行要快。
94 |
95 | 编程语言的出现让计算机编程变成一件非常惬意的工作,实现同一功能但使用更加精巧、更加快速的程序也变成了可以向他人炫耀的资本。最终的结果是,人类的智慧可以更好地融入到程序当中。这一方面极大提高了生产效率,另一方面也推动了计算机科学自身的发展。
96 |
97 | 上个世纪70年代,计算机科学家们开始设计一种称为“操作系统”的计算机程序。这种计算机程序为计算机和程序员之间提供了一种交互能力,借此人们还可以充分使用计算机的计算能力。编写程序的工作绝大部分情况下需要相当长的时间,而计算机运行起来却很快。因此,计算机作为一个庞然大物,大部分时间是在等待人们输入新的程序。这明显是个非常大的浪费。因此,计算机科学家们开始琢磨,是否可以让许多人同时共用一台计算机?
98 |
99 | 于是,作为美国军方和贝尔实验室一个计划的一部分,UNIX 操作系统被设计出来,同时被设计出来的还有 C 语言。而设计 UNIX 的两个主要工程师[^9],则在后来被授予图灵奖——这个计算机科学领域的最高成就奖。
100 |
101 | UNIX 操作系统对管理现代电子计算机的资源(计算能力、存储等)带来了划时代的影响,不论是其后出现的 BSD、Linux 等 UNIX 变种操作系统还是微软公司的 Windows 系列操作系统,都沿用了其中的大部分概念,如进程、文件、目录树等等,甚至最近出现的 Android、iOS 等面向智能设备的操作系统也无出其左右。
102 |
103 | 操作系统,将计算机硬件的处理器、存储等组成单元抽象为对应的程序对象,如进程、文件系统、文件等,一方面将上层的计算机程序和繁杂的底层汇编级操作隔离开来,另一方面,文件、管道等的引入,使得程序间的协作变成可能。
104 |
105 | 有了操作系统,我们可以使用自己喜欢的任意编程语言编写程序,然后借助解释器或者编译器编译后执行自己的程序。我们编写程序的方法也从直接操作硬件变成了读取和写入文件这种抽象的操作,而且无需关心计算机系统资源(如处理器、内存)等的使用,操作系统会帮我们管理这些资源,在不同的正在运行的程序(进程)之间进行调配。
106 |
107 | 那么,分层设计到底给计算机程序带来了什么?为什么会出现一个称为“操作系统”的软件?在笔者看来,本质上,操作系统实现了如下两个关键功能:
108 |
109 | - 计算机系统资源的抽象。比如将存储或者输入和输出设备抽象为文件。
110 | - 应用程序编程接口(API,application programming interface)。通过提供现成可用的接口集合,实现了程序的复用,一方面提高了编程效率,另一方面使得程序的内部结构变得更为清晰。
111 |
112 | 但无论如何分层,或者如何设计,计算机程序所能完成的功能无外乎两个:接收输入并按照自己的规则或算法完成输出。
113 |
114 | ## 计算机程序的本质功能和执行原理
115 |
116 | 在阐述本节议题之前,我们先给软件及程序一个定义。先说比较容易定义的程序。程序是指最终可在计算机上执行的算法集合,通过编译或者解释成计算机可以识别的编码序列而执行。
117 |
118 | 软件则相对比较难以定义。从宽泛的意义上讲,任何包含有序数据的文件或者文件集合都可以称为软件,其关键点在于有序数据,也就是信息(纯粹的无序数据集合无法表达有意义的讯息)。比如一个字体文件,也可以称为是计算机软件,而只有该字体文件被一个渲染字体的程序读取并显示出文字的样子,这个软件才有了起真正的价值。
119 |
120 | 我们通常所理解的软件,可以从狭义上定义为包含程序和各种有序数据(信息)在内的集合。因为程序本身也是一个数据序列,因此,软件又可以定义为:一个有序数据(信息)集合,其中的某个或多个数据序列可以被计算机执行。
121 |
122 | 本质上,计算机程序所做的工作就是接收输入然后按照自己的算法进行处理,之后产生输入。这和数学上的公式基本上属于同一类东西。
123 |
124 | 我们以阿兰·图灵的图灵机作为示例来阐述计算机程序的工作原理。以下是百度百科针对“图灵机”这一词条给出的解释:
125 |
126 | > 所谓的图灵机就是指一个抽象的机器,它有一条无限长的纸带,纸带分成了一个一个的小方格,每个方格有不同的颜色。有一个机器头在纸带上移来移去。机器头有一组内部状态,还有一些固定的程序。在每个时刻,机器头都要从当前纸带上读入一个方格信息,然后结合自己的内部状态查找程序表,根据程序输出信息到纸带方格上,并转换自己的内部状态,然后进行移动。
127 |
128 | 我估计大部分的读者看到这段解释,几乎得不到太多有价值的内容,因为我们仍然无法清晰理解图灵机到底是个什么东西。为此,笔者在这里给大家换个更容易理解的方式来解释图灵机。
129 |
130 | 图灵机是阿兰·图灵在 1936 年(那时图灵年仅二十四岁)时,为研究“实数的可计算性”这一命题而设计出来的。可计算的实数是指,这个实数可以通过给定的初始条件,通过一定的规则(或公式)计算出来。比如,对自然数5,我们可以给定 4 这个初始值和 1 这个加数,通过简单的加法计算出来,也可以通过勾三股四这个公式计算出来。理解了可计算数这个定义,我们凭直觉就可以回答:整数、有理数[^10]都是可计算的。然而,实数中占绝大多数的无理数[^11]却不这么简单。根据常识,有些我们常见的无理数,比如 π 可以被计算,但其他更多的无理数是不是可以通过一个给定的条件和规则计算出来则不能轻易下结论。
131 |
132 | 图灵借助于图灵机的研究结果表明,不是所有的实数都是可计算的。当然,这个结论对笔者以及本书的绝大多数读者来讲没有多大的意义,我们知道有这样一个结论就可以了。
133 |
134 | 我们回过头来看图灵设计的图灵机,看看为了解决实数的可计算性问题,图灵是如何一步步设计出图灵机的。
135 |
136 | 我猜想图灵首先会尝试用逻辑证明的是圆周率 π 是可计算的,毕竟 π 这个无理数具有较好的代表性,事实上,图灵的确给出了使用图灵机计算π的方法(就是算法,或者程序)。历史上出现过多种计算 π 的值的方法,从阿基米德的几何逼近法到分析法等等。有关圆周率的计算方法,读者可自行搜索互联网。
137 |
138 | 但不论使用哪种方法计算 π,其实本质上无外乎要这么做:
139 |
140 | 1. 给定一个初始条件(输入),使用一个公式来计算该初始条件下的值(输出);
141 | 2. 然后以该值作为输入,或停止计算,或重复第一个步骤。
142 |
143 | 要假设使用一个机器来完成这个计算,那我们大致可以针对上述步骤提出如下需求:
144 |
145 | 1. 这个机器可以读取输入,也可以写下输出,还可以将自己的输出再次读取成为输入。
146 | 2. 这个机器使用一定的规则根据输入做一些处理,并将结果输出出来;在我们这里就是按照给定的公式进行计算。
147 | 3. 以上两点还蕴含一个要点,那就是机器识别输入所使用的符号;也就是说,需要设计一种符号体系,可以方便机器识别。
148 |
149 | 于是,图灵机就被设计为前述那个样子。其要点可以如《信息简史》作者所描述的那样总结如下:
150 |
151 | 1. 纸带。图灵将图灵机使用的纸带想象成一条无限长的一维的长纸条,上面是一个个格子,格子里边包含一个符号。图灵机可以读取、擦除或者写入特定的符号。纸带给以图灵机一个输入装置和临时或者永久的存储装载,而如何物理地存储这些符号则被忽略。以电子计算机为例,纸带相当于内存或者硬盘等存储设备。
152 | 2. 符号。符号及其特定的排列顺序定义了我们前述的“编码”。比如我们可以用十进制的阿拉伯数字来表示数字。在随后改进的图灵机上,图灵最终使用了 0 和 1 这两个最简单的符号,以二进制的方式来表示各种数字。
153 | 3. 状态。状态本质上可以理解为某种处理规则,它决定了针对当前由图灵机读取的符号,图灵机应该执行哪个或者哪几个动作,如前后移动纸带、擦除符号、写入新的符号、停机等等。给定某个特定的计算工作,图灵机的状态是有限的,如果将这些状态保存成一个表,则这个状态表对应于现代意义上的计算机的指令集。
154 |
155 | 尽管图灵机最初的设计目标仅仅是为了一个数学上的证明,然而,图灵机的出现却给其后的数学和信息论发展起到了很大的作用,现代电子计算机甚至将来的量子计算机本质上就是图灵机或者多个或简单或复杂的图灵机之有机结合,只是有如下两个主要区别:
156 |
157 | - 不论哪种计算机,其存储空间(纸带)并不是无限的;
158 | - 不论哪种计算机,执行程序都要消耗能量,其运算能力是有上限的。
159 |
160 | 当我们编写计算机程序时,时时刻刻都要受到上面两点的约束。
161 |
162 | 言归正传,图灵机不仅仅可以计算实数,通过编码(建立数字和其他现实物体之间的映射关系),图灵机可以做任何事情。比如,打印这本书的工作,大致可以用下面这台图灵机来完成:
163 |
164 | 图灵机不停读取以特定格式保存的编码序列,其中包含了这本书的文字以及格式(比如页眉、页脚、文字的大小、段落之间的分隔符等等),然后计算出每个字在纸面上要打印的位置,移动打印头,并根据这个字的字型[^12]数据,控制打印头打印出文字的字型来。
165 |
166 | 上面这台图灵机,其实就是现代针式或者激光打印机的工作原理。其中有两种形式的编码:
167 |
168 | 1. 用来控制文档内容和格式的编码。
169 | 2. 用来控制文字字型的编码。
170 |
171 | 甚至,我们可以将不同的图灵机组合起来使用,让某类图灵机专门做一件事情,而其他类型的图灵机做另外一件事情,前者的输出成为后者的输入。于是,我们现在看到的各种各样的电子设备就出现了。比如现代的电子计算机系统中,中央处理单元(CPU)是其中处于核心地位的图灵机,而包含在图形显示卡中的图形处理单元(GPU)则是另外一个复杂的图灵机。甚至在某种程度上,我们还可以将很多计算机外设(如硬盘、网卡、键盘等)视作简单的图灵机。当然,像鼠标这种外设恐怕很难被称为图灵机,毕竟其中不包含状态的概念,鼠标这种设备仅仅通过光电或者机械的方式将用户对鼠标的移动、按钮的点击等动作转换为计算机可以识别的某种编码,这类设备可以称为“编码器”或“解码器”。键盘之所以可以视作图灵机,是因为现在我们日常使用的键盘可以实现组合键、按键转义等相对较为复杂的逻辑功能。
172 |
173 | 更进一步,通过网线、USB 线或者无线网络,我们还可以将这些图灵机连接起来,在其中传输编码,这不仅仅可用来在人和人之间快速传输信息,同时也可以完成更为复杂和抽象的工作,比如流行的云端存储。哎呀,这不就是互联网吗?!
174 |
175 | [^1]: 数字计算机:Digital Computer。“数字的(Digital)”一词和“模拟的(Analogous)”相对应。传统的电话将声音转换为强弱变化的电流并经线路传输,然后再在接收端通过扬声器播放出来,人们认为这种处理办法是模拟的,这通常意味着“信息没有损失”;而大家现今日常使用的手机,则是将声音信号在非常短的时间内进行采样,将这些采样出来的数字一个个发送到接收方,然后再将这些数字组装并通过扬声器播放。后面这种经过采样的处理方法就称为“数字化处理”。但实际上,相比模拟而言,数字化的处理办法可以提供更好的防噪声及抗干扰能力,也便于存储和处理。这点,在香农于 1948 年发表在《贝尔系统技术期刊》中的《通信的数学理论》中已经过数学上的证明。这篇论文也被视为信息论的开山之作。
176 |
177 | [^2]: 信息:Information。可以简单理解为数据;最近被科学家视为一种新的、可度量的物理量,详情可见《信息简史》一书。
178 |
179 | [^3]: 【维基百科】查尔斯·巴贝奇(Charles Babbage,1791~1871),英国数学家、发明家兼机械工程师。
180 |
181 | [^4]: 对该议题感兴趣的读者可以阅读《数学:确定性的丧失》、《时间简史》、《上帝掷骰子吗?》、《信息简史》等著作。
182 |
183 | [^5]: 艾达·洛夫莱斯(Ada Lovelace,维基百科译作埃达·洛夫莱斯)。艾达是著名诗人拜伦的女儿,遗憾的是拜伦和其母在艾达一个月时分居,此后再也未能见面。艾达死后和其父葬在一起。另外,Ada 编程语言就是为纪念艾达而命名的。
184 |
185 | [^6]: 图灵机是由阿兰·图灵(Alan Mathison Turing,又译作艾伦·图灵)于1936年在其论文《论可计算数及其在判定性问题上的应用》中提出的一种虚拟机器,被视为现代计算机的理论模型
186 |
187 | [^7]: 一个简单的例子。在智能手机上,普通应用无法读取屏幕上的内容,而在 PC 上,却没有任何限制。
188 |
189 | [^8]: 为何采用二进制表示数字,读者可参阅本书第一章。
190 |
191 | [^9]: 【维基百科】肯·汤普逊(Ken Thompson),生于美国新奥尔良,计算机科学学者与软件工程师。他与丹尼斯·里奇(Dennis MacAlistair Ritchie,逝于2011年)设计了B语言、C语言,创建了Unix和Plan 9操作系统,他也是编程语言Go的共同作者。与丹尼斯·里奇同为1983年图灵奖得主。
192 |
193 | [^10]: 有理数是可计算的,是因为有理数包括整数、有限小数和无限循环小数;而有限小数和无限循环小数都可以表示为某两个整数的商。
194 |
195 | [^11]: 无理数其实要比有理数多得多得多!笔者大学的一个数学老师曾使用一个形象的比喻来说明这个事情。假设所有的实数均匀分布于一张纱窗上,有理数的数量就是这个纱窗上的点,而其他位置弥漫的全部都是无理数。这个比喻给我的印象之深,使我在几十年后对这位老师讲解这个形象比喻的场景仍然历历在目。
196 |
197 | [^12]: 从代表某个文字的编码得到这个文字的具体表现样子(字型)的过程,称为字型渲染。我们熟知的字体文件保存了这些文字或字符的字型信息。
198 |
199 |
--------------------------------------------------------------------------------
/textbook/toc.md:
--------------------------------------------------------------------------------
1 | # 目录
2 |
3 | @SUBJECT [计算机软件技术导论](toc.md)
4 | @AUTHOR [魏永明](https://github.com/VincentWei)
5 | @DATE 2022-03-12
6 | @LANG 简体中文
7 |
8 | *版权声明*
9 |
10 | 版权所有 © 2022 魏永明
11 | 保留所有权利
12 |
13 | - [引言](foreword.md)
14 | - [前言](preface.md)
15 | - 第一篇:信息的计算机表述。
16 | - [第 1 章 为什么是二进制?](part-1-chapter-1.md)
17 | - [第 2 章 二进制及其运算。](part-1-chapter-2.md)
18 | - [第 3 章 数:整数及浮点数。](part-1-chapter-3.md)
19 | - [第 4 章 文字:字符集及编码。](part-1-chapter-4.md)
20 | - [第 5 章 多媒体:图像及音视频。](part-1-chapter-5.md)
21 | - [第 6 章 抽象对象及结构化数据。](part-1-chapter-6.md)
22 | - [第 7 章 有关信息表述的方法总结。](part-1-chapter-7.md)
23 | - 第二篇:信息的计算机存储。
24 | - 第 8 章 文件系统。
25 | - 第 9 章 关系数据库。
26 | - 第 10 章 NoSQL 数据库。
27 | - 第 11 章 分布式存储。
28 | - 第 12 章 有关信息存储的方法总结。
29 | - 第三篇:信息的计算机处理。
30 | - 第 13 章 常见算法。
31 | - 第 14 章 压缩及加密。
32 | - 第 15 章 大数据处理。
33 | - 第 16 章 人工智能。
34 | - 第 17 章 有关信息处理的方法总结。
35 | - 第四篇:信息的计算机展现。
36 | - 第 18 章 字体。
37 | - 第 19 章 矢量图形。
38 | - 第 20 章 HTML 及 CSS。
39 | - 第 21 章 图形界面及交互。
40 | - 第 22 章 典型文件格式。
41 | - 第 23 章 有关信息展现的方法总结。
42 | - 第五篇:信息的计算机传输。
43 | - 第 24 章 互联网及 TCP/IP 协议。
44 | - 第 25 章 常见应用层协议。
45 | - 第 26 章 远程过程调用及数据随动。
46 | - 第 27 章 物联网及相关传输协议。
47 | - 第 28 章 有关信息传输的方法总结。
48 | - 第六篇:计算机编程语言。
49 | - 第 29 章 编程语言的本质及分类。
50 | - 第 30 章 面向对象编程。
51 | - 第 31 章 Web 编程。
52 | - 第 32 章 设计新的编程语言。
53 | - 第七篇:操作系统。
54 | - 第 33 章 通用操作系统。
55 | - 第 34 章 实时操作系统。
56 | - 第 35 章 智能设备操作系统。
57 | - 第 36 章 操作系统的本质及未来。
58 | - 第八篇:软件工程方法。
59 | - 第 37 章 软件工程方法的演进。
60 | - 第 38 章 敏捷开发模型。
61 | - 第 39 章 开源软件及开源协作模型。
62 | - 第 40 章 没有唯一、普适的软件工程方法。
63 | - 第九篇:计算机软件技术的发展热点。
64 | - 第 41 章 新的编程语言。
65 | - 第 42 章 新的WEB开发技术或框架。
66 | - 第 43 章 下一代操作系统。
67 | - 第 44 章 人工智能及大数据。
68 | - 第 45 章 云计算。
69 | - 第 46 章 虚拟化技术。
70 | - 后记:计算机软件技术发展的哲学思考。
71 | - 附录 A:伪代码及其语法。
72 | - 附录 B:重要或易混淆术语。
73 | - 附录 C:程序或算法索引。
74 |
75 |
--------------------------------------------------------------------------------