编码
距离上次更新博文已经是很久以前了,一个是忙,另一点是懒,最近这一周节奏才恢复正常,愿意, 且有时间看看资料, 同时分享, 总结。
前言
仅仅看标题可能依然会走偏,不太明白我究竟想要表达些什么。
这是一本书的名字,《编码》,看完这本书之后,才算是对我每日所做的工作有了另一种层面上的认知与理解。才算是明白,在这之前,之后的一长段的时间内,都仍然只能算是一个码农。远远算不得一个合格的程序员,这条路很长,还有很长的时间要走。
编码,这本书的名字是编码,并不是通常意义上的敲代码,或是字符编码,又或是其他概念。概括来说,是讲解如何从零开始构建一台计算机,从计算机硬件,外设,到软件,计算机系统,一步步搭建。
什么是编码
从旗语,交通信号灯等其他抽象的概念,到手语,盲文这些和语言更贴近一些的概念,再到汉字,英文等等这些更为完善,统一的标准语言。再到身为开发者所最经常接触到的种种字符集,乃至二进制0,1编码等等 都是属于编码的范畴。
编码本身就是将概念认知,事物,行为等东西抽象出来,用相应的文字,光线,色彩,触觉等生物或机器所能识别出来的方式进行 等意 转换,并且是可逆转换 而形成的统一认知标准。 无论这统一的范围有多大,对一个人而言,又或是所有人而言。只要在这个范围内,通过这种方式编码形成的信息, 就能够被解读。
在世界上,如果我们能将每一种概念都与一个数字相对应, 从负无穷到正无穷。并且可以肯定的是, 一定能够实现这点,只在工作量的大小。当我们得到这样一个一一对应的字符集之后,便能够用数字表示我们所见到的所有事物。换句话说,我们对世界进行了编码,整个世界都被抽象化了。
但这样的数字无疑太复杂,无论是谁想要完全记忆并解读这样的事物,都显得不太可能。我们将编码围缩小,不用记忆一切的事物,只需要那些常规化的,需要发现事物的统一规律,用这些最基础的事物,组合起来构建复杂事物即可。
我们会发现,而正是因为具有这样的规律性,这样高度的可重复性,自动化机械化变成了可能。
为什么是二进制
在现如今,机器所能解读的编码都是二进制编码,也就是我们所熟知的0,1。
但为什么要采取这种形式, 而不是0,1,2?这样的三进制编码呢?
以下均是个人理解。
我觉得要从几方面来看,一方面,是电流的特性,在电路中只有通和断两种状态。另一方面也是事物的特性所决定的,在逻辑认知中,只存在两种可能性,是 或 否,而不存在介于两者之间的事物。对待任何一个判断,只有是和非两种状态,而没有第三种介于两者之间的可能性。对于选择题而言,会有多种结果,对判断题来说,在特定的环境下,答案只有对错。我们的认知方式决定了这一点。加之电流的特性本身决定了这一点,对机器的解读而言, 二进制是目前的最优解。
只要能够表示不同,就能够将所有事物进行编码。即使是一位, 仅仅用0进行编码,我们也能够用 0 00 000表示三种不同的状态。但这样事物却是难以被解读的,当它存在于机器,文本的时候, 没有谁能够知道 00000000究竟表示什么意思。而就像在 UTF-8编码中所做的一样, 每四位表示一个字符。 则 0001 0010就很容易被区分开来。
怎样进行加减法运算
当在电路中通过开关 或 电流的有无, 高低电平, 分别表示1, 0两种对应的编码, 在这里就不研究种种复杂的电路究竟是如何实现相应的功能。
仅仅研究二进制的加法运算。
为了让这种运算更切合实际, 能够应用在机器的电路开关等方面上。首先得提到一个机器所能进行的操作只有从0-1 从 1-0, 无论是多少位数字。在数字的任意位置都可以进行这项操作。
这又不得不提到几个比较有趣的逻辑运算符, 与或非以及异或运算, 对这几种运算在现实生活中都存在相应的 逻辑门(也就是电路组合)当相应的电流通过特定的门, 根据输入的不同产生预期的结果。
也正是因为这种逻辑电路,才让用电流进行加法运算变成了可能。
1 + 1 = 0 0 + 0 = 0在这里进行的是异或运算。
1 + 0 = 1 0 + 1 = 0 在这里进行的是或运算。
1 + 1 需要进位1这里进行的是 与 运算。
通过这种方式便实现了其对应的加法运算。
那么减法运算该怎么实现呢?
当进行加法运算的时候, 一位一位向上加, 当存在进位,将进位也当做输入放入下一次的计算中。通过这种方式得到最终结果。
但是就小学所学到的知识来看,进行减法运算时,需要一个额外的操作,借位操作, 更何况:
1 - 1 = 0, 0 - 0 = 0, 1 - 0 = 1 0 - 1 = 1 同时借位为1, 如果进行这种操作意味着我们需要为减法专门再设计一套相应的逻辑门。同时需要额外的一位表示借位。这种操作方式不够理想。 最理想的莫过于能够借用现有的已实现的操作进行减法运算。
方法1:
1100 - 0001 = (1111 - 0011) - 0001 = 1111 - (0011 + 0001);
对被减数取反, 与减数求和, 结果再度取反。
方法2:
1100 - 0001 = 1100 - (1111 - 1110)= 1100 + 1110 - 1111;
而为了避免借位:
一般会 = 1100 - (10000 - (1110 + 1)) = 1100 + 1110 + 1 - 10000
对减数取反, 与被减数求和, + 1 之后减去 10000.
通过这种方式可以将 符号位巧妙的融入算法之中, 而前一种方法则不行。大致是因为,通过+1 使得数字强行被扩大,向前进一位。
因此后一种方式也是实际中所采用的方式进行减法运算。仅通过加法与取反操作就实现了减法运算。
而后将这种特定的加法,减法等逻辑运算写为相应的电路,进而一步步扩大,集成,就成了我们所见到的富含各种功能的芯片, 电路板。
数据的存储与转换怎样实现?
这部分是涉及到计算机的核心部分,数据无论是存储在硬盘,纸带,又或者其他地方,都不是一件很难的事情。我们有准备,有计划的将特定数据写入。这并不难。
但如何能够将计算机在运行过程中产生的数据,电信号转换为可以长久存储或暂时存储的数据呢?
当开关一直闭合或断开,这种持续性输出的电流是无法被转换为我们想要的数据的,就像是在之前提到的编码中所说的一样。我们可以用0 00 000 表示不同的数字。但是这种数据很难被解读, 无论是用固定位,又或是其他的方式, 因为当只用0表示时,我们无法找到一个合适的字符来表示空。要知道,在计算机中,空格本身也是用编码所表示的。所以至少需要两位来进行编码转换对应。
而在数据存储方面,如果电路持续输出1 11 111,这种方式,计算机又如何知晓究竟该在何时进行数据的存储操作呢?这是一件很难的事情。总不能够在每一秒(毫秒, 纳秒)进行一次数据的存储,会产生太多的无效数据,这样的输出本身也是没有意义的。因此就需要一个标识电流,标识同时指示, 需要存储什么数据到什么地址。这就是 代码所做的事情, 这里的代码指的是汇编代码,汇编代码会被转换为机器码,控制计算机进行相关的操作。
而数据的存储,从根源上来说是将电流当前的状态长期保存下来,且在标识电流改变之前,不再受输入的影响。这里就牵扯到一个概念, 锁存。在一开始用的还是电流本身,在现在寄存器中使用的是电容,并且每隔一段时间都会进行充电。防止电流失,导致数据变化。而这里的电流电压有无并不是绝对意义上的有 无。
而是在一定范围内的电压被称为低电压, 高电压, 也就表示0,1.
而这里需要提到的一个问题就是:
如何将代码转换为机器码,机器码并不是供人阅读的。而代码,即使是010101表示的代码,依然是人类可读的代码。
那么这输入的种种代码究竟是怎样被转换成机器码的?
这里又是坑爹的个人理解,没有实际依据:
当无论是从键盘输入,从远程读取,又或是其他任何方式获取到的数据,或代码,在本质上依然是高低平的电流(这电流无论是通过电磁波,光信号,声音信号等种种方式转换成电流输入)。计算机读取到这些信号,用作输入信号,执行相应的功能。 至于我们所看到的代码, if else , 种种其他东西,甚至于看到的汇编代码,都是显示器的功劳, 将特定的电信号转换为对应的色彩显示在屏幕上。
至于键盘输入,也不过类似于按下了一个个特定的开关,控制电流。
几个核心概念
时钟,程序计数器, 总线
在这里面有几个对我而言比较新鲜,但却是很重要的概念。
时钟,时钟频率也就是电脑的主频。对不同的CPU其主频是不相同的,这是用来衡量电脑性能的一个很重要的指标。
但该怎样理解这个时钟的功效呢?就我目前的浅薄认知而言,当每次需要读取,存储数据的时候,即使是将数据从一个寄存器转移到另一个寄存器,都需要进行相关的读写操作,也叫IO操作,而数据的存储记录就是当每次时钟状态变换的时候,进行相关的IO操作,从小处来看,每一次的数据读写操作都需要时钟变换,而上升来看,每一条指令的执行都伴随着若干次的读写操作,所以会根据指令的复杂程度,需要的时钟周期也有所区别。
程序计数器 是用来存放下一条指令所在单元的地址的地方。它是计算机处理器中的寄存器,它包含当前正在执行的指令的地址(位置)。并且每执行一条,程序计数器的存储地址加一。 通过这种方式得以按照顺序一条条执行相应的指令。
总线:计算机除了 CPU以外还有许多的输入输出设备,除此以外还有集成在电路板上的各种功能的集成电路。这些数据最终都是要放入CPU进行统一调度,处理。而总线,就是CPU和这些设备的信号交互渠道。
定点数、浮点数
除了计算机本有的种种字符集之外,通过这些方式表示字符是没有问题的, 如ASCII, UNICODE, GBK等其他字符集。
但是如何表示一个数字,如何对相应的数字进行运算,对于一个ASCII编码的字符串我们是没有办法直接进行计算的, 必须转换为相应的数值,才能运算。在计算机中也就有两种表示方式。
一种是定点数:
定点数又有定点整数和定点小数的区别。 对于定点数而言,其最高位表示的都是符号位,定点整数的小数点固定在最低位的右边,定点小数的小数点都是固定在最高数据位的左边, 小数点前边再设一位符号位。
而定点数的存储是比较特别的, 它可以表示无限长的数字,将一个数字的每一位都拆开,拆成4bit的数据,如 9 就是 1001, 而0则是 0000, 901就表示为:
1001 0000 0001,通过每四位表示一个数字的方式就可以表示无限大的数字了,而无需考虑究竟怎么转换成二进制数,同时,数字的读取本身也按照这种方式来进行。
第二种是浮点数:
浮点数应用的方式是科学技术法, 不论是32位浮点数,还是64位双精度浮点数。都是采取这种方式来表示数字, 不同的是指数位的长度和有效位数的长度,至于整数位均为1. 所以忽略不写。
而通过这种方式就会导致一个比较有趣的问题,浮点数表示的数字不够精确。简单来说, 因为每一位都是 2^n次方这种方式, 所以介于最小精度两者之间的数字就无法表示了, 如在 32位浮点数时, 有效位数为23位, 2^-23 和 2^-24之间的数字是无法表示的,而无论这个指数究竟有多么巨大。 通过浮点数表示出来的结果, 是相同的。
所以在涉及金钱相关的数据时,基本都会采用定点数的方式进行存储,计算。
顺便一提,在 java中,定点数是 BigDecimal
图像究竟是什么?
我们在日常生活中,经常会见到各种各样的图片格式, JPG,JEPG,BMP,PNG。
但在计算机中,这些又是怎样被显示, 解释的呢?
显卡,这是将计算机内的数据转换为图像所使用的, 读取数据并写入寄存器中,通过电子枪将光束打入光栅内部,通过这种方式,将图像显示出来。我们可以将屏幕想象成一个巨大的矩形阵列,其中每个格子都被称为一个像素,同时每一个像素都是由一个或多个比特表示。
在这里采用三原色的方式,每种色彩取值从0~255种可能性。总计需要3个字节来表示一个像素,而对平常使用的 1280 * 1024 就需要3.9 MB的内存才能够表示完全。
而图像本身就是对每一个像素的定义,定义其色彩,又通过种种算法对图像进行压缩,使得其在不失真的情况下尽可能的减小存储空间。
而各种各样的图像格式就是不同的压缩算法,甚至是数据的存储方式不同。所以对于将图像表示成base64这种编码格式,也就没有什么值得惊奇的地方了。
太晚了, 也就不再写总结了。