追根究底的习惯是深度分析和解决问题、提升程序员素养的关键所在,有助于编写高质量的代码。基础知识的深度认知决定着知识上层建筑的延展性。
1.1 走进0与1的世界
原码、反码和补码
在二进制的世界里,表示数的基本编码方式由原码、反码和补码三种:
- 原码:符号位与数字实际值的结合。正数数值部分是数值本身,符号位为0;负数数值部分是数值本身,符号位为1。8位二进制数的表示范围是[-127,127]。
- 反码:正数数值部分是数值本身,符号位为0;负数数值部分是在正数表示的基础上对各个位取反,符号位为1。8位二进制数的表示范围是[-127,127]。
- 补码:正数数值部分是数值本身,符号位为0;负数数值部分是在正数表示的基础上对各个位取反后加1,符号位为1。8位二进制数的表示范围是[-128,127]。
为什么会有反码和补码的表达方式呢?
为了加速计算机对加减乘除的运算速度,减少额外的识别成本。
以减法运算为例,减去一个数等于加上这个数的负数。在计算机中延续这种计算思维,不需要额外做符号位的识别。但是使用原码计算的结果不正确,所以出现了反码的编码方式。但是在某些特殊情况下,使用反码会出现-0的结果,实际上0不存在+0和-0两种表达方式,所以诞生了补码。
补码的出现除解决运算的问题外,还带来额外的好处,即在占用相同位数的条件下,补码的表达区间比前两种编码的表达区间更大。
单位
计算机中,1位即1个bit,简写为b。8个bit组成一个单位,称为一个字节,即1个Byte,简写为B。1024个Byte,简写为KB;1024个KB,简写为MB;1024个MB,简写为GB。
位移运算
除二进制的加减法外,还有一种操作:位移运算。
在左移<<与右移>>两种运算中,符号位均参与移动,除负数往右移动,高位补1之外,其他情况均在空白处补0。
左移运算由于符号位参与向左移动,在移动后的结果中,最左位可能是1或者0,即正数向左移动的结果可能是正,也可能是负;负数向左移动的结果同样可能是正也可能是负。
>>>无符号向右移动。当向右移动时,正负数高位均补0,正数不断向右移动的最小值是0,而负数不断向右移动的最小值是1。位移运算仅作用于整形(32位)和长整型(64位)数上,假如在整型数上移动的数字是字长的整数倍,无论是否带符号位以及移动方向,均为本身。
其他操作
按位取反~、按位与&、按位或|、按位异或^。
逻辑与运算&&和逻辑或运算||具有短路功能。
1.2 浮点数
计算机定义了两种小数,分别为定点数和浮点数。
浮点数采用科学计数法来表示,由符号位、有效数字、指数三部分组成。
1.2.1 科学计数法
浮点数是计算机用来表示小数的一种数据类型。在数学中,采用科学计数法来近似表示一个极大或极小且位数较多的数。科学计数法的有效数字为从第1个非零数字开始的全部数字,指数决定小数点的位置,符号表述该数的正负。
1.2.2 浮点数表示
浮点数表示就是如何用二进制数表示符号、指数和有效数字。当前业界流行的浮点数标准是IEEE754,该标准规定了4种浮点数类型:单精度、双精度、延伸单精度、延伸双精度。因为浮点数无法表示零值,所以取值范围分为两个区间:正数区间和负数区间。
用于存储符号、阶码、尾数的二进制位分别为符号位、阶码位、尾数位。
- 符号位:在最高二进制位上分配1位表示浮点数的符号,0表示正数,1表示负数。
- 阶码位:在符号位右侧分配8位来存储指数,IEEE754标准规定阶码位存储的是指数对应的移码。移码是将一个真值在数轴上正向平移一个偏移量后得到的,即$[x]_移 = x + 2^{(n-1)}$(n为x的二进制位数,含符号位)。由于阶码实际存储的是指数的移码,所以指数与阶码之间的换算关系就是指数与它的移码之间的换算关系。假设指数的真值为e,阶码为E,则$E = e + (2^{(n-1)} - 1)$。
- 尾数位:最右侧分配连续的23位用来存储有效数字,IEEE754标准规定尾数以原码表示。
科学计数法进行规格化的目的是保证浮点数的唯一性。
1.2.3 加减运算
- 零值检测:阶码与尾数都为0则浮点数为0
- 对阶操作:IEEE754规定对阶的移动方向为向右移动
- 尾数求和
- 结果规格化:尾数向右移动称为右规,反之成为左规
- 结果舍入
1.2.4 浮点数使用
在使用浮点数时推荐使用双精度。
在要求绝对精确表示的业务场景下,推荐使用整型存储其最小单位的值,展示时可以转换成该货币的常用单位。
1.3 字符集与乱码
在ASCII码种,有两个特殊的控制字符10和13,前者是LF即“\n”,后者是CR即“\r”。
实现Unicode的编码格式有三种:UTF-8、UTF-16、UTF-32。
在日常开发中,字符集如果不兼容则会造成乱码。数据流从数据库到应用层,到web服务器,再到客户端都会碰到字符乱码的问题,排查起来是一个比较长的链路。
1.4 CPU与内存
CPU(Central Processing Unit)是一块超大规模的集成电路板,是计算机的核心部件,承载着计算机的主要运算和控制功能,是计算机指令的最终解释模块和执行模块。
CPU内部的处理机制是由控制器和运算器组成的,内部寄存器使这两者协同更加高效。
-
控制器
控制器由控制单元、指令译码器、指令寄存器组成。其中控制单元是CPU的大脑,由时序控制和指令控制等组成;指令译码器是在控制单元的协调下完成指令读取、分析并交由运算器执行等操作;指令寄存器是存储指令集,当前流行的指令集包括X86、SSE、MMX等。
-
运算器
运算器的核心是算术逻辑运算单元,即ALU,能够执行算术运算或逻辑运算等各种命令,运算单元会从寄存器中提取或存储数据。
-
寄存器
最著名的寄存器是CPU的高速缓存L1、L2。
计算机中的所有程序都在内存中运行,它的容量与性能如果存在瓶颈,即使CPU再快,也是枉然。内存物理结构由内存芯片、电路板、控制芯片、相关支持模块等组成,内存芯片结构比较简单,核心是存储单元,支持模块是地址译码器和读写控制器。
1.5 TCP/IP
1.5.1 网络协议
TCP/IP(Transmission Control Protocol/Internet Protocol),传输控制协议/因特尔互联协议。
-
链路层:链路层以字节为单位把0和1进行分组,定义数据帧,写入源和目标机器的物理地址、数据、校验位来传输数据
MAC地址长6个字节共48位,使用十六进制表示,前24位由管理机构统一分配,后24位由厂商自己分配,保证网卡全球唯一
-
网络层:子网内根据地址解析协议(ARP)进行MAC寻址,子网外进行路由转发数据包,这个数据包即IP数据包
-
传输层:UDP和TCP。UDP是面向无连接的,是不可靠传输,多用于视频通信、电话会议等。TCP是面向连接的,是一种端到端间通过失败重传机制建立的可靠数据传输方式,给人感觉是一条固定的通路承载着数据的可靠传输
-
应用层:传输层的数据到达应用程序时,以某种统一规定的协议格式解读数据
程序在发送消息时,应用层按既定的协议打包数据,随后由传输层加上双发的端口号,由网络层加上双方的IP地址,由链路层加上双方的MAC地址,并将数据拆分成数据帧,经过多个路由器和网关后,达到目标机器。
1.5.2 IP协议
IP地址属于网络层,主要功能在WLAN内进行路由寻址,寻址最佳路由。
TTL(Time To Live),数据包的生存时间,表示IP报文被路由器丢弃之前可经过的最多路由总数。
ICMP(Internet Control Message Protocol),它是检测传输网络是否通畅、主机是否可达、路由是否可用等网络运行状态的协议。
1.5.3 TCP建立连接
传输控制协议是一种面向连接、确保数据在端到端间可靠传输的协议。
TCP的FLAG由6个bit组成,分别代表AYN、ACK、FIN、URG、PSH、RST,都以置1表示有效。我们重点关注SYN、ACK、FIN。SYN(Synchronize Sequence Numbers)用作建立连接时的同步信号;ACK(Acknowledgement)用于对收到的数据进行确认,所确认的数据由确认序列号表示;FIN(Finish)表示后面没有数据需要发送,通常意味着所建立的连接需要关闭了。
三次握手指的是建立连接的三个步骤:
- A机器发出一个数据包并将SYN置1,表示希望建立连接。这个包中的系列号假设为x
- B机器收到A机器发过来的数据包后,通过SYN得知这是一个建立连接的请求,于是发送一个响应包并将SYN和ACK都置1。假设这个包中的序列号是y,而确认序列号必须是x+1,表示收到了A发过来的SYN。在TCP中,SYN被当作数据部分的一个字节
- A收到B的响应包后需要确认,确认包中将ACK置1,并将确认序列号设置为y+1,表示收到了来自B的SYN
为什么需要第三次握手呢?主要是两个目的:信息对等和防止超时。第三次握手后,B机器才能确认自己的发报能力和对方的收报能力是正常的。TTL网络报文的生存时间往往都会超过TCP请求超时时间,如果两次握手就可以创建连接,传输数据并释放连接后,第一个超时的连接请求才到达B机器的话,B机器会以为是A创建新连接的请求,然后确认同意创建连接。因为A机器的状态不是SYN_SENT,所以直接丢弃了B的确认数据,以致最后只是B机器单方面创建连接完毕。这就出现了请求超时导致的脏连接。
TCP在协议层面支持Keep Alive功能,即隔段时间通过向对方发送数据表示连接处于健康状态。
1.5.4 TCP断开连接
A机器想要关闭连接,则待本方数据发送完毕后,传递FIN信号给B机器。B机器应答ACK,告诉A机器可以断开,但是需要等B机器处理完数据,再主动给A机器发送FIN信号。这时,A机器处于半关闭状态(FIN_WAIT_2),无法再发送新的数据。B机器做好连接关闭前的准备工作后,发送FIN给A机器,此时B机器也进入半关闭状态(CLOSE_WAIT)。A机器发送针对B机器FIN的ACK后,进入TIME_WAIT状态,经过2MSL(Maximum Segment Lifetime)后,没有收到B机器传来的报文,则确认B机器已经收到A机器最后发送的ACK指令,此时TCP连接正式释放。
- TIME_WAIT:主动要求关闭的机器表示收到了对方的FIN报文,并发送出了ACK报文,进入TIME_WAIT状态,等2MSL后即可进入CLOSED状态。如果FIN_WAIT_1状态下,同时收到FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无需经过FIN_WAIT_2状态。
- CLOSE_WAIT:被动关闭的机器收到对方请求关闭的FIN报文,在第一次ACK应答后马上进入CLOSE_WAIT状态。这种状态其实表示在等待关闭,并且通知应用程序发送剩余数据,处理现场信息,关闭相关资源。
TIME_WAIT貌似是百害而无一利的,为何不直接关闭,进入CLOSED状态呢?
第一,确认被动关闭方能够顺利进入CLOSED状态。
第二,防止失效请求。
1.5.5 连接池
在客户端与服务器之间可以事先创建若干连接并提前放置在连接池中,需要时可以从连接池直接获取,数据传输完成后,将连接归还置连接池中,从而减少频繁创建和释放连接所造成的开销。
秒级的SQL查询通常存在巨大的性能提升空间:
- 建立高效且合适的索引
- 排查连接资源未显式关闭的情形
- 合并短的请求
- 合理拆分多个表join的SQL,若是超过三个表则禁止join
- 使用临时表
- 应用层优化
- 改用其他数据库
1.6 信息安全
1.6.1 黑客与安全
互联网企业都要建立一套完整的信息安全体系,遵循CIA原则,即保密性(Confidnetiality)、完整性(Integrity)、可用性(Availability)。
- 保密性:对需要保护的数据进行保密操作,无论是存储还是传输,都要保证用户数据及相关资源的安全。
- 完整性:访问的数据需要是完整的,而不是缺失的或者被篡改的,不然用户访问的数据就是不正确的。
- 可用性:服务需要是可用的。
1.6.2 SQL注入
预防SQL注入,应从以下几个方面考虑:
- 过滤用户输入参数中的特殊字符,从而降低被SQL注入的风险
- 禁止通过字符串拼接的SQL语句,严格使用参数绑定传入的SQL参数
- 合理使用数据库访问框架提供的防注入机制
1.6.3 XSS与CSRF
跨站脚本攻击,Cross-Site Script,是指黑客通过技术手段,向正常用户请求的HTML页面中插入恶意脚本,从而可以执行任意脚本。在防范XSS上,主要通过对用户输入数据做过滤或者转义。
1.6.4 CSRF
跨站请求伪造(Cross-Site Request Forgery),在用户并不知情的情况下,冒充用户发起请求,在当前已经登录的WEB应用程序上执行恶意操作,如恶意发帖、修改密码、发邮件等。
防范CSRF漏洞主要通过以下方式:
- CSRF Token验证,利用浏览器的同源限制,在HTTP接口执行前验证页面或者Cookie中设置的Token,只要验证通过才能继续执行请求。
- 人机交互,比如在调用是网上银行转账接口时校验短信验证码。
1.6.5 HTTPS
HTTPS的全称是HTTP over SSL,简单的理解就是在之前的HTTP传输上增加了SSL(安全套接字,Secure Socket Layer)协议的加密能力。
非对称加密的安全性是基于大质数分解的困难性。
1.7 编程语言的发展
- 机器语言时代
- 高级语言时代
- 自然语言时代