1.问题
1、什么是位,字节,字符,字符集,字符串?
2、为什么一个字节长度表示的范围是[-128,127],为什么不是对称的?
3、为什么计算机无法精确表示小数?
4、原码,反码,补码知多少?
5、GBK,UTF-8,ASCII,UNICODE经常见到,是什么意思,有何区别?
6、声音是如何存储在计算机里面的,图像,视频呢?
2.关键词
信息,载体,编码,解码,规范 位,字节,字符,字符串(字符集), 服务器,浏览器(meta charset),文本编辑器,文本编码,乱码,数字编码,补码原理
3.全文概要
本文从信息表示作为开端讲起,由浅及深列出各种程序员熟视无睹而且模糊不清的概念。试图深入剖析个中由来,理清计算机底层概念模型,从而加深对计算机基础的认识。
计算机底层硬件表示出来的信息只有0和1两个数字,用一串0和1进行组合来表示不同的信息。为了便于交流,人们就制定了一套规则,规定用固定长度,不用组合来表示数字和文字,从而形成最早的ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)编码方案。而随着计算机的普及,计算机编码方案也经历了从本地化编码到国际化编码的发展,最终形成Unicode的一统天下编码方案,考虑到网络传输的效率,UTF编码方案也应运而生。而乱码也就是不同编码方案混淆使用的结果。对于数字的运算,由于计算机底层硬件实现加法比较容易,所以人们又进一步采用补码编码方案,来规范数字的计算。
4、编码综述
人类发明了语言,大大提升了信息传递的效率。但是其实大部分动物也有属于自己的语言,唯一不同的是人类不但使用语言传递信息,还发明了文字来记载信息,这才大幅度的提升了信息的传播效率,而且还能用于保存信息。
文字是记载信息的载体,那么最原始的纸张就是保存信息的介质。到了上世纪五十年代开始计算机的发明,信息的传播渠道更加丰富。人与人之间通过语言交流,通过文字交流,通过文字保存信息。那么计算机也需要有自己的语言才能交流啊。从物种的角度来看,计算机就像是地球上的一个新物种一样,跟其他动物没什么两样(信息交互角度)。
那么计算机就有自己的文字系统,计算机与计算机之间交流(UTF-8编码)需要用它们自己的语言。平时我们跟动物交流会通过表情,动作,声音等方法,比如你在逗你家的狗狗,但这种交流是低级的,只能停留在娱乐的逗玩,无法深层次交流,你可以说你家狗狗很懂你,但她真的不知道你你余额宝还有多少钱。
问题来了,人与计算机这个物种需要深层次交流,计算机有自己的语言,人类有自己的语言,彼此不懂对方,就需要一套规则,来约定彼此之间的交流。那么编码技术就是这样产生的。计算机语言用一连串1和0组合起来表示不同得信息。
那么人类和计算机是如何交流的呢,比如:让计算机打印一句话“我要吃饭”
'我要吃饭'(Unicode编码)
翻译成计算机语言就是这样了,那么我们能做的就是定一套规章制度,规定人类语言对应的二进制数字,而且要一一对应,形成双射。从信息层面比较的话,就跟翻译成'i want eating'是一样的(假设计算机懂英文)。
5.数字编码
5.1 整数
首先,如果这世界只有整数,而且只有加法,那就简单很多,清静很多了。但是如果只有整数和加法,那世界就会少了很多精彩,也没有如此强大的计算机给你用了。
我们先来说说整数,整数可以分为正整数和负整数,光有数字也没用,要参与运算才能体现出价值,那么我们先从加减法说起。我们知道减一个正数等于加上该正数绝对值的负数,由于现阶段计算机CPU擅长做加法运算,CPU硬件实现减法要复杂得多,而且运算效率很低,所以我们偷懒只讨论加法运算。说不定以后发明了减法加速硬件,那就没有下文的介绍了。
那么关键的一个环节就是如何将减法等价的转换为加法:
比如 x - y = x + (-y)
那么就要解决一个问题,将带符号的-y,转换为不带符号的Y:
也即 -y = Y
因为我们知道计算机只认得0和1这两个数字,现实中所有的信息最终只能用按照约定的顺序来排列0和1,而对于数字来说,我们无法在0或者1前面加个负号来表示负数,这时候就有人提出了一个约定,就是考虑一个负数,先用二进制数字表示它的绝对值,再将数字的第一位用来表示数字的正负(0开头表示正数,1开头表示负数),用4位二进制数字来举例:
3(10)=0011(2)−3(10)=1011(2)
很好,看样子也不是什么难事,减法负数的问题分分钟解决,但是好景不长,我们马上遇到一个大麻烦,用4位二进制数字表示,请看:
5(10)=0101(2)−3(10)=1011(2)
5 - 3 = 5 + (-3) = 0101 + 1011 = 10000 = 0000 = 0 (4位二进制相加溢出结果截断保留后4位)
而5 -3 = 2,这不科学,这证明我们约定的这套规则是有漏洞的,因为我们只需要举出一个反例就可以否决这套方案是行不通的,也就是,我们反对之前提到的约定,简单的将绝对值第一位取反来表示负数是不科学的!其实我们还能举一个更简单的例子来说明这套方案是有多坑爹的:
0000(2)=0(10)1000(2)=−0(10)
我们现实生活中的数字系统,并不存在-0这个东东,所以这个例子也可以否决掉那套方案。
因为运算符号跟数字系统用同样的符号表示,直接加就会导致问题。
那么,有什么办法来解决这个难题呢?已有的思路要求我们寻求一套数字运算解决方案,这套方案必须满足如下条件:
这套方案里面的数字必须跟十进制数字系统的数字一一对应,形成双射;
这个方案里面的数字运算结果,必须和十进制数字系统运算结果也一一对应,形成双射;
只能用1和0来表示;
现有十进制运算可以借助 '-' ,'+'等符号来辅助参与运算,但是计算机只认0和1,这样看起来有点难度。于是经过很久的探索,又有人提出一套改进版的数字映射方案,称为反码。
反码定义:正数的反码为其本身,负数反码除了符号位将所有位置的数字取反。
那我们先来试试做下运算
原码转换:
5(10)→0101(2)3(10)→0011(2)
反码转换:
−3(10)→1100(2)
5 - 3 = 5 + (-3) = 0101 + 1100 = 10001 = 0001 = 1 (4位二进制相加溢出结果截断保留后4位)
而5 -3 = 2
同样否决掉!
最后经过苦苦思索,终于提出我们现在在使用的一套数字解决方案,称为补码。
补码定义:
正数的补码为其本身,负数补码除了符号位将所有位置的数字取反,再加1.
符号位权的总和,符号位权值为2的n次方,最高位权值带符号
数字系统的模减去数字本身得出的数字就是补码
那我们先来试试做下运算
原码转换:
5(10)→0101(2)3(10)→0011(2)
补码转换:
−3(10)→1101(2)
5 - 3 = 5 + (-3) = 0101 + 1101 = 100010 = 0010 = 2 (4位二进制相加溢出结果截断保留后4位)
这是5 -3 = 2 对了,但是我们不能光举个正确的例子就说明这套数字方案是可行的,必须经过严谨的证明才行。
证明要符合我们之前提到的三个准则:
在n位二进制数字系统中,他的模2^n也是确定的,那么在模的范围呢,数字本身是唯一的,那模减去该数字得出的补码也是唯一的;
既然补码是唯一的,那么计算结果也是唯一的;
都是用1和0表示;
但是这样的证明还是太过粗浅,首先给出补码的定义:
下面证明补码加法的合理性,即证明以下公式:
B2Tw(x⃗+y⃗)=B2Tw(x⃗)+B2Tw(y⃗)
证明:
根据补码的定义,观察如下表数据
十进制 | 数符号位+ 二进制绝对值的表示方式 | tones’ complement | two’s complement |
+7 | 0111 | 表示方式不变 | 表示方式不变 |
+6 | 0110 | 表示方式不变 | 表示方式不变 |
+5 | 0101 | 表示方式不变 | 表示方式不变 |
+4 | 0100 | 表示方式不变 | 表示方式不变 |
+3 | 0011 | 表示方式不变 | 表示方式不变 |
+2 | 0010 | 表示方式不变 | 表示方式不变 |
+1 | 0001 | 表示方式不变 | 表示方式不变 |
+0 | 0000 | 表示方式不变 | 表示方式不变 |
-0 | 1000 | 表示方式不变 | 表示方式不变 |
-1 | 1001 | 1110 | 1111 |
-2 | 1010 | 1101 | 1110 |
-3 | 1011 | 1100 | 1101 |
-4 | 1100 | 1011 | 1100 |
-5 | 1101 | 1010 | 1011 |
-6 | 1110 | 1001 | 1010 |
-7 | 1111 | 1000 | 1001 |
-8 | 超出4个bit所能表达范围 | 超出4个表达范围 | 1000 |
根据补码的定义,4位二进制的范围就能得出来是[-128,127],这就是为什么取值范围不是对称的原理。
那是如何得出补码这套编码方案呢?
下面我们一步一步来推导补码方案的原理上的进化。
为什么用原码来运算有问题,用补码这种编码方案来运算就没问题呢?其实二进制运算的本质都一样的,不管用什么编码方案,二进制加法过程都不会变,只不过是运算得出的结果在不同的编码方案中表示不同的数字。
原码只是简单的采用二进制的首位作为符号位,起到标明正负号的作用,整个运算过程这个符号位除了标明正负作用,并无其他功能,或者说没用参与到原码系统的运算过程,比如:
0001(2)→1(10)1001(2)→−1(10)0001(2)+1001(2)=1010(2)→−2(10)≠1(10)+(−1(10))=0(10)→0000(2)
以上例子说明的就是符号位在原码编码系统的运算里面除了标明正负,并不参与运算。
如果要校正原码编码系统,那就需要加入一些约定或者说规则,让
0001(2)+(−1)(10)=0000(2)
如何实现?
符号位不参与到运算过程
先给出同余原理:
两个整数a,b,若它们处于正整数m除得的余数相等,则称a,b对于模m同余,记做 a ≡ b mod m。
也就是说符号位独立存在,只用于符号标记。用4位二进制数字表示如下:
根据同余原理:
5−3≡5+((−3)mod8)=5+5≡10mod8=20101+1011≡0101+0101=0010=2(10)(∵011mod1000=101)
算法过程:先同余转换,将负数等价转换为正数(因为正数运算符号占位符不影响计算),只运算前三位,超出三位的数字溢出丢弃,第一位仅作为符号标识,根据绝对值大小比较确定正负号。
补码直观由来可以看如下计算过程,范围为[0,999],数据溢出按同余处理
也就是634 - 249等同于634 + (-249)也就是加一个负数,上文计算过程本质就是
634 + 750 + 1 = 1385 ≡ 1385 mod 1000 = 385
750就是249的相反数(反码),然后再加1,也即是反码+1=补码
这个过程推广到二进制一样成立。
符号位参加运算
相比上一种算法繁杂的过程,如果能够将符号位纳入运算过程,那算法会不会简单一点呢?
我们引入位权的概念,二进制数与数字本身和它的位置有关,同样的1在第一位和第三位表示的大小是不一样的,我们用2^(n-1)
来表示n位二进制各个位置的权重,称为位权。
那么一个数字就是所有位权的总和,比如负数首位用1表示,那么位权也为负数
这样首位标识位除了标明正负号,本身还有权重,可参与运算,比如:
5−3=5+(−3)→0101+1101=0010→2
原码表示如下:
310=00112(−3)10=10112
那么如何从原码的 -3 = 1011 变换到 1101呢?
原码 -> 反码 -> 补码
1011 -> 1100 -> 1101这就是反码的由来。
5.2 小数(浮点数)
小数计算为何会有误差?
先看十进制浮点数的表示
dmdm−1⋯d1d0d−1d−2⋯d−m
我们很熟悉十进制的科学计数法比如
那么二进制的浮点数也采用同样的方法来表示
也就是二进制浮点数小数点向左移动一位相当于这个数被2除。
我们知道十进制的1/5这个分数可以用0.2精确表示,但二进制确无法精确表示.因为二进制小数表示只能用固定的数字组合,如下表:
所以十进制的0.2只能近似用如上数字来表示。
6.文本编码
第一阶段:初始化
由于计算机是由老美发明的,那么理所当然用他们的语言来跟计算机做映射。英文有26个字母,加大写字母,加符号,也才一百多个,所以美国人制定了一套编码规则来表示他们的语言,就是ASCII编码规则,有127个符号。ASCII计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。
第二阶段:本地化
计算机普及后,广大的世界人民也要用计算机啊,总不能要求他们都懂英语吧,所以需要扩展计算机编码规则,在他们地区内扩展,也叫本地化编码,这些不同国家地区自顾自的制定编码规则,造成跨国交流非常混乱。比如中文世界里的编码就要GBK,GB2312,GB18030,BIG5。’GB‘就是国标的缩写,还有韩文日文的编码更多。
第三阶段:国际化
这样下去肯定不行,全球标准部门就说我们来搞一套大家都认可的编码系统,于是就有了Unicode编码规则了,称国际通用字符集为UCS。为了使国际间信息交流更加方便,国际组织制定了UNICODE字符集,为各种语言中的每一个字符设定了统一并且唯一的Code Point,以满足跨语言、跨平台进行文本转换、处理的要求。
第四阶段:效率化
现在字符已经统一了,在网络传输需要兼顾效率,就有了UTF(UCS Transformation Format)编码规则(可变长规则)整部编码史的发展,也记录着文本数据记录方式的变迁。那么现在就可以很轻松的面对乱码了,计算机对字符的处理分为两个阶段,当编码和解码的规则不一样是就会产生乱码。解码的时候如果没有显示指定编码规则,那计算机就会更加文本开端的字符去猜测编码规则。著名的“联通”乱码问题就是这样产生的。
乱码是什么?我们先来看它的定义。维基百科对乱码的定义是:电脑系统不能显示正确的字符,而显示其他无意义的字符或空白。这个定义包含了两层含义,第一是文本本身采用的编码规则,第二是要显示文本的工具采用的编码规则,当这两种规则不一致(或者不兼容)的时候乱码就产生了。现在我们清晰的知道计算机内部存储文本的机制,现实中经常碰到的乱码问题就可以顺藤摸瓜解决了。比如:
浏览器把GBK编码的源文件当成是BIG5编码来显示;
数据库把UTF-8编码的中午数据当成ISO-8859-1(Latin-1)来存储;
代码IDE把gbk源码当成UTF-8编码打开;
内存数据流把GB2312编码的输入文本当成GBK编码来读取;
WEB服务器(tomcat)设置的编码跟URL传输的编码不一致;
这些本质上都是编码规则冲突才造成的乱码。那么我们自由细心检查数据流程的每一个环节的编码规则是否一致,就能避免乱码的产生。比如我们熟悉的web请求流程,从url发起一个http请求,经过如下环节:
HTML编码:meta标签里面的charset设置编码;
浏览器编码:设置浏览器渲染HTML文本的编码格式;
网关代理:接收http请求的代理,转发数据过程对数据的压缩,如nginx;
WEB容器:响应http请求的容器,接收请求参数如tomcat配置server.xml;
WEB应用:通常以web.xml为入口,上下文编码设置;
IDE编辑器:源码编码格式,控制台输出文本格式;
数据库:分为服务端,客户端,连接器,数据库等模块的字符集,需要统一
这些过程都必须保证编码规则一致,通常采用UTF-8来规范,至此我们对文本编码已经有了立体的认知了,具体问题以此为指导思想,相信都可以迎刃而解。
7.多媒体编码
7.1存储音频(时间)
音频采样,在一定时间间隔内采集有限的样本来表示,采样率跟跟模拟信号变化频率有关,通常每秒40000个样本。
采集完需要量化,截取样本数据整数部分。
编码,样本位深度表示样本数值,
R=S x B=40000x16=640000b/s=640KB/s
编码标准,MP3有损压缩
7.2存储图像(空间)
光栅图:解析度(单位面积像素数),色彩深度(像素位数量,三原色为每种原色为8位,共有2^24种颜色),索引色用于减少存储像素位数。
矢量图:由数学公式计算像素分布。
7.3存储视频(帧)
帧,由图片构成,多帧构成视频流。