程序 = 算法 + 数据结构程序员
对应到计算机的组成原理(硬件层面)算法
计算机用0/1组成的二进制,来表示全部信息数据结构
万物在计算机里都是0和1,搞清楚各类数据在二进制层面是怎么表示的,是咱们的必修课。编码
在实际应用中最常遇到的问题,也就是文本字符串是怎么表示成二进制的,特别是咱们会遇到的乱码到底是怎么回事儿加密
在开发的时候,所说的Unicode和UTF-8之间有什么关系。spa
理解了这些,相信之后遇到任何乱码问题,你都能手到擒来了。调试
二进制和咱们平时用的十进制,并无本质区别,只是平时是“逢十进一”,这里变成了“逢二进一”code
每一位,相比于十进制下的0~9这十个数字,咱们只能用0和1这两个数字。orm
任何一个十进制的整数,都能经过二进制表示出来blog
把一个二进制数,对应到十进制,很是简单,就是把从右到左的第N位,乘上一个2的N次方,而后加起来,就变成了一个十进制数
固然,既然二进制是一个面向程序员的“语言”,这个从右到左的位置,天然是从0开始的。
好比_0011_这个二进制数,对应的十进制表示,就是
\(0×2^3+0×2^2+1×2^1+1×2^0\)
\(=3\)
表明十进制的3
对应地,若是咱们想要把一个十进制的数,转化成二进制,使用短除法就能够了
也就是,把十进制数除以2的余数,做为最右边的一位。而后用商继续除以2,把对应的余数紧靠着刚才余数的右侧,这样递归迭代,直到商为0就能够了。
刚才咱们举的例子都是正数,对于负数来讲,状况也是同样的吗?
咱们能够把一个数最左侧的一位,当成是对应的正负号,好比0为正数,1为负数,这样来进行标记。
这样,一个4位的二进制数, 0011就表示为+3。而1011最左侧的第一位是1,因此它就表示-3。这个其实就是整数的原码表示法
原码表示法有一个很直观的缺点就是,0能够用两个不一样的编码来表示,1000表明0, 0000也表明0。习惯万事一一对应的程序员看到这种状况,必然会被“逼死”。
因而,咱们就有了另外一种表示方法。咱们仍然经过最左侧第一位的0和1,来判断这个数的正负。可是,咱们再也不把这一位当成单独的符号位,在剩下几位计算出的十进制前加上正负号,而是在计算整个二进制值的时候,在左侧最高位前面加个负号。
好比,一个4位的二进制补码数值1011,转换成十进制,就是
\(-1×2^3+0×2^2+1×2^1+1×2^0\)
\(=-5\)
若是最高位是1,这个数必然是负数;最高位是0,必然是正数。而且,只有0000表示0,1000在这样的状况下表示-8。一个4位的二进制数,能够表示从-8到7这16个整数,不会白白浪费一位。
固然更重要的一点是,用补码来表示负数,使得咱们的整数相加变得很容易,不须要作任何特殊处理,只是把它当成普通的二进制相加,就能获得正确的结果。
咱们简单一点,拿一个4位的整数来算一下,好比 -5 + 4 = -1,-5 + 6 = 1
咱们各自把它们转换成二进制来看一看。若是它们和无符号的二进制整数的加法用的是一样的计算方式,这也就意味着它们是一样的电路。
不只数值能够用二进制表示,字符乃至更多的信息都能用二进制表示
最典型的例子就是字符串(Character String)
最先计算机只须要使用英文字符,加上数字和一些特殊符号,而后用8位的二进制,就能表示咱们平常须要的全部字符了,这个就是咱们经常说的ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码)
ASCII码就比如一个字典,用8位二进制中的128个不一样的数,映射到128个不一样的字符里
好比,小写字母a在ASCII里面,就是第97个,也就是二进制的0110 0001,对应的十六进制表示就是 61。而大写字母 A,就是第65个,也就是二进制的0100 0001,对应的十六进制表示就是41。
在ASCII码里面,数字9再也不像整数表示法里同样,用0000 1001来表示,而是用0011 1001 来表示。字符串15也不是用0000 1111 这8位来表示,而是变成两个字符1和5连续放在一块儿,也就是 0011 0001 和 0011 0101,须要用两个8位来表示。
咱们能够看到,最大的32位整数,就是2147483647。若是用整数表示法,只须要32位就能表示了。可是若是用字符串来表示,一共有10个字符,每一个字符用8位的话,须要整整80位。比起整数表示法,要多占不少空间。
这也是为何,不少时候咱们在存储数据的时候,要采用二进制序列化这样的方式,而不是简单地把数据经过CSV或者JSON,这样的文本格式存储来进行序列化。不论是整数也好,浮点数也好,采用二进制序列化会比存储文本省下很多空间。
ASCII码只表示了128个字符,一开始倒也堪用,毕竟计算机是在美国发明的
然而随着愈来愈多的不一样国家的人都用上了计算机,想要表示譬如中文这样的文字,128个字符显然是不太够用的。因而,计算机工程师们开始各显神通,给本身国家的语言建立了对应的字符集(Charset)和字符编码(Character Encoding)
表示的能够是字符的一个集合
好比“中文”就是一个字符集,不过这样描述一个字符集并不许确
想要更精确一点,咱们能够说,“初版《新华字典》里面出现的全部汉字”,这是一个字符集。这样,咱们才能明确知道,一个字符在不在这个集合里面
好比,咱们平常说的Unicode,其实就是一个字符集,包含了150种语言的14万个不一样的字符。
则是对于字符集里的这些字符,怎么一一用二进制表示出来的一个字典
咱们上面说的Unicode,就能够用UTF-八、UTF-16,乃至UTF-32来进行编码,存储成二进制。因此,有了Unicode,其实咱们能够用不止UTF-8一种编码形式,咱们也能够本身发明一套 GT-32 编码,好比就叫做Geek Time 32好了。只要别人知道这套编码规则,就能够正常传输、显示这段代码。
一样的文本,采用不一样的编码存储下来。若是另一个程序,用一种不一样的编码方式来进行解码和展现,就会出现乱码。这就好像两个军队用密语通讯,若是用错了密码本,那看到的消息就会不知所云。在中文世界里,最典型的就是“手持两把锟斤拷,口中疾呼烫烫烫”的典故。
没有经验的同窗,在看到程序输出“烫烫烫”的时候,觉得是程序让CPU过热发出报警,因而尝试给CPU降频来解决问题。
既然今天要完全搞清楚编码知识,咱们就来弄清楚“锟斤拷”和“烫烫烫”的前因后果。
若是咱们想要用Unicode编码记录一些文本,特别是一些遗留的老字符集内的文本,可是这些字符在Unicode中可能并不存在。因而,Unicode会统一把这些字符记录为U+FFFD这个编码
若是用UTF-8的格式存储下来,就是\xef\xbf\xbd。若是连续两个这样的字符放在一块儿,\xef\xbf\xbd\xef\xbf\xbd,这个时候,若是程序把这个字符,用GB2312的方式进行decode,就会变成“锟斤拷”。这就比如咱们用GB2312这本密码本,去解密别人用UTF-8加密的信息,天然没办法读出有用的信息。
而“烫烫烫”,则是由于若是你用了Visual Studio的调试器,默认使用MBCS字符集
“烫”在里面是由0xCCCC来表示的,而0xCC又刚好是未初始化的内存的赋值。因而,在读到没有赋值的内存地址或者变量的时候,电脑就开始大叫“烫烫烫”了。
到这里,相信你发现,咱们能够用二进制编码的方式,表示任意的信息。只要创建起字符集和字符编码,而且获得你们的认同,咱们就能够在计算机里面表示这样的信息了。因此说,若是你有心,要发明一门本身的克林贡语并非什么难事。
不过,光是明白怎么把数值和字符在逻辑层面用二进制表示是不够的。咱们在计算机组成里面,关心的不仅是数值和字符的逻辑表示,更要弄明白,在硬件层面,这些数值和咱们一直提的晶体管和电路有什么关系。下一讲,我就会为你揭开神秘的面纱。我会从时钟和D触发器讲起,最终让你明白,计算机里的加法,是如何经过电路来实现的。