为什么80%的码农都做不了架构师?>>>
程序是怎样跑起来--读书笔记
需要重点了解的一些问题
- 进制转换:二进制,八进制,十六进制
- 补码, 原码, 反码
- 位操作:左移,右移,与,非,否,异或
- 浮点数: 科学计数法,单精度,双精度,与0进行比较
- 字节序
- 大尾,小尾/ 大端小端
- 内存对齐,为什么要对齐
- 指令集: x86,x64区别,其他指令集
- 加解密: 对称加密,非对称加密,散列,都有哪些。
一些进制技巧性例子
任意长度对齐
对齐一般使用在2的倍数位,如16位,,当然其他位也可以,只是用的不多
例子:
a = 7
a = (a + 0xF) & ^0xF
a = ?
//结果
0000 0111 //7
0000 1111 //0xF
0001 0110
1111 0000 //^0xF
0001 0000 //16
任意长度对齐, 假设对齐位数为n.
数学公式:
(a + (n-1)) % n //计算末尾数目
a + n - ((a + (n-1)) % n ) //对齐数据
=> 优化
(a + 0xF) &^ 0xF)
两者是等价的,按位进行计算要比 取余%, 除法/ cpu调用效率高很多倍
去掉最后一位
例子
x = 129
x = x & (x-1)
x = ?
//结果
1000 0001 //129
1000 0000 //128
-1 在cpu计算的是补数 1111 1111,相加以后
1000 0001
1111 1111
1 1000 0000
首位的1, 被丢弃
判断一个数的奇偶性
if num & 1 != 0 {
fmt.Println("奇数")
}else{
fmt.Println("偶数")
}
一次hash校验
使用一次hash 判断一个时间段内的验证数据是否正确,也就是验证一个数据生成的token,是否正确
如,电话号+验证码 生成一个验证token. 当下次返回token, 我们在一个时间段内判断数据是否正确,不适用数据库,或者其他进行缓存数据的方式
两次hash的方式
sha1 (phone + code + 时间段 + salt )
首先取当前 time.Now().Unix() 时间戳, 然后将后面比如 5分钟 除以后,将时间以5分钟分为一段一段的,然后传递给客户端
当客户端返回结果时, 依然使用当前时间 减去这个尾数,进行 哈希计算, 看是否相等,如果不相等, 那么就再减去 5分钟,就是判断是否是前一个时间段的,再进行哈希比较,如果一致,那么认为是对的,不一致那么传来的数据就是错误的。
一次hash解决的方式
sha1 (phone + code + 时间段 + salt )
当前时间 ts = time.Now().Unix()
diff = ts/300 获得一个时间段是奇数还是偶数,取其中一位作为标识bit, 如就取slice得低位。
hash[0] &= byte(0xFE) // 将第一个byte最后一bit,设置为0. 3 & 0xFE = 0000 0011 & 1111 1110 = 0000 0010 最后一位为0
hash[0] |= byte(diff & 1) //diff&1 的到最后一个是奇数,还是偶数, 然后| 将最后这bit, 放到上面已经为0的 hash[0] 中。
一般约定
0xbfa 0x 16 进制 010 0 8 进制 1b b/B 2 进制 10 10 进制
16进制字符=> 10进制
'b'-'a' + 10 = 11 //0xb => 11
字符集
- ASCII是最早最通用的单字节编码系统
- GB2312: 是对ASCII进行了扩展
- GBK: B2312有局限性,只能表示6000个字符,GBK是对GB2312的升级
- Unicode : unicode编码表中包含了世界上所有国家的所有字符和符号的编码。unicode编码是一种概念.
- UTF-8 : TF-8是unicode编码的一种实现。
- UTF-16 : UTF-16和UTF-8是彻底不同的两种编码概念。
- UTF-32 : UTF-32可以说是“真正”的unicode编码
ASCII、GB2312、GBK、Unicode、UTF-8、UTF-16 编码方式比较分析
cpu 内部结构
- 寄存器可用来暂存指令、数据等处理对象,可以将其看作是内存的一种.根据种类的不同,一个 CPU 内部会有 20~100 个寄存器
- 控制器负责把内存上的指令、数据等读入寄存器, 并根据指令的执行结果来控制整个计算机。
- 运算器负责运算从内存读 入寄存器的数据
- 时钟负责发出 CPU 开始计时的时钟信号。不过,也 有些计算机的时钟位于 CPU 的外部。
内存
通常所说的内存指的是计算机的主 存储器(main memory),简称主存。主存通过控制芯片等与 CPU 相连, 主要负责存储指令和数据。 主存由可读写的元素构成,每个字节(1 字 节 = 8 位 )都带有一个地址编号
CPU 可以通过该地址读取主存中的 指令和数据,当然也可以写入数据。 但有一点需要注意,主存中存储 的指令和数据会随着计算机的关机而自动清除。
程序启动后,根据时钟信号,控制器会从内存中读取指令 和数据。通过对这些指令加以解释和运行,运算器就会对数据进行运算,控制器根据该运算结果来控制计算机。
寄存器
CPU 的四个构成部分中,程序员只需要了解寄存器即可。
程序是把寄存器作为对象来描述的。
种 类 | 功 能 |
---|---|
累加寄存器(accumulator register) | 存储执行运算的数据和运算后的数据 |
标志寄存器(flag register) | 存储运算处理后的 CPU 的状态 |
程序计数器(program counter) | 存储下一条指令所在内存的地址 |
基址寄存器(base register) | 存储数据内存的起始地址 |
变址寄存器(index register) | 存储基址寄存器的相对地址 |
通用寄存器(general purpose register) | 存储任意数据 |
指令寄存器(instruction register) | 存储指令。CPU 内部使用,程序员无法通 过程序对该寄存器进行读写操作 |
栈寄存器(stack register) | 存储栈区域的起始地址 |
对程序员来说,CPU 是什么呢? CPU 是具有各种 功能的寄存器的集合体
程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器都只有一个,其他的寄存器一般有多个
数据是用二进制表示的
二进制表示的原因
计算机内部是由I CA 这种电子部件构成的。 CPU(微处理器)和内存也是 IC 的一种。
引脚在 IC 内部并排排列着。IC 的所有引脚,只有直流电压 0V 或 5VB 两个状态。也就是说,IC 的一个引脚,只能表示两个状态。
IC 的这个特性,决定了计算机的信息数据只能用二进制数来处理。
计算机处理信息的最小单位位 ,就相当于二进制中的一位。位的英文 bit 是二进制数位 (binary digit)的缩写。
8 位二进制 数被称为一个 字节。
字节是最基本的信息计量单位。位是最小单位, 字节是基本单位。 内存和磁盘都使用字节单位来存储和读写数据,使用位单位则无法读写数据。因此,字节是信息的基本单位。
用字节单位处理数据时,如果数字小于存储数据的字节数(= 二进 制数的位数),那么高位上就用 0 填补。
对于用二进制数表示的信息,计算机不会区分它是数值、文字, 还是某种图片的模式等,而是根据编写程序的各位对计算机发出的指 示来进行信息的处理(运算)。
二进制数
○○的 ×× 次幂”表示位权,其中,十进制数的情况下○○部分为 10,二进制数的情况下则为 2。这个称为基数。
○○的 ×× 次幂”中的 ××,在任何进制数中都是数的位数-1。即第1位是1-1 = 0次幂,第2位是2-1 = 1次幂,
移位运算和乘除运算的关系
移位运算 指的是将二进制数值的各数 位进行左右移位(shift = 移位)的运算。移位有左移(向高位方向) 和右移(向低位方向)两种。 在一次运算中,可以进行多个数位的移 位操作。
<< 运算符和 >> 运算符的左侧是被移位 的值,右侧表示要移位的位数。
- 左移运算:空出来的低位要进行补 0 操作。
- 右移运算:见补数
移位操作使最高位或最低位溢出的数字,直接丢弃就可以了。
二进制数左移后就会变成 原来的 2 倍、4 倍、8 倍......反之,二进制数右移后则会变成原来的 1/2、1/4、1/8......这样一来,就能代替 乘法运算和除法运算。
补数(补码)
右移后空 出来的高位的数值,有 0 和 1 两种形式。
二进制数中表示负数值时,一般会把最高位作为符号来使用,因 此我们把这个最高位称为符号位。 符号位是 0 时表示正数 ,符号位是 1 时表示负数。
计算机在做减法运算时,实际上内部是在做加法运算。用加法运算来实现减法运算。
在表示负数时就需 要使用“二进制的补数”。 补数就是用正数来表示负数,
为了获得补数,我们需要将二进制数的各数位的数值全部取反。
用 8 位二进制数表示-1 时,只需求得 1, 也就是 00000001 的补数即可。具体来说,就是将各数位的 0 取反成 1, 1 取反成 0,然后再将取反的结果加 1,最后就转化成了 11111111
00000001+11111111 确实为 0(= 00000000)。这个运算中出现 了最高位溢出的情况,对于溢出的位, 计算机会直接忽略掉。100000000 这个 9 位二进制数就会被认为是 00000000 这一 8 位二进制数。
-
补数求解的变换方法: 就是“取反+1”
-
将二进制数的值取反后加 1 的结果,和原来的值相加,结果为 0
-
当运算结果为负数时,计算结果的值也 是以补数的形式来表示的
比如 3-5 这个运算,用 8 位二进制数表示 3 时 为 00000011, 而 5 = 00000101 的 补 数 为“取 反+1”, 也 就 是 11111011。00000011 + 11111011 的运算结果为 11111110,最高位变成了 1.
假 若 11111110 是负△△,那么 11111110 的补数就是正△△。通过求解补 数的补数,就可知该值的绝对值。
11111110 的补数,取反加 1 后为 00000010。这个是 2 的十进制数。因此,11111110 表示的就是-2。
逻辑右移和算术右移的区别
- 逻辑右移: 当二进制数的值表示图 形模式而非数值时,移位后需要在最高位补 0。类似于霓虹灯往右滚动 的效果。这就称为 逻辑右移
- 算术右移: 将二进制数作为带符号的数值进行运算时,移位后要在最高位填 充移位前符号位的值(0 或 1)。这就称为算术右移
如果数值是用补 数表示的负数值,那么右移后在空出来的最高位补 1,就可以正确地 实现 1/2、1/4、1/8 等的数值运算。如果是正数,只需在最高位补 0 即可。
将-4(= 11111100)右移两位。这 时,逻辑右移的情况下结果就会变成 00111111,也就是十进制数 63, 显然不是-4 的 1/4。而算术右移的情况下,结果就会变成 11111111, 用补数表示就是-1,即-4 的 1/4
只有在右移时才必须区分逻辑位移和算术位移。左移时,无论是 图形模式(逻辑左移)还是相乘运算(算术左移),都只需在空出来的 低位补 0 即可。
- 符号扩充 :就是指在保持值不变的前提下将其转换成 16 位和 32 位的二进制数。
将 01111111 这个正的 8 位二进制数转换成 16 位二进制数时,很容易就能 得出 0000000001111111 这个正确结果
像 11111111 这样用补数来 表示的数值,将其 表示成 1111111111111111 就可以了。
不管是正数还是用补数表示的负数,都只需用符号位的值((0 或者 1)填充高位即可。
逻辑运算(与,或,非,异或)
- 算术运算是指加减乘除四则运算
- 逻辑运算是指对二进制数各数字位的 0 和 1 分别进行处理的运算,包括逻辑非(NOT 运算)、逻辑与(AND 运算)、逻辑或(OR 运算)和逻辑异或(XOR 运算)四种
XOR 是英语 exclusive or 的缩写。有时也将 XOR 称为 EOR
- 逻辑非: 指的是 0 变成 1、1 变成 0 的取反操作。
- 逻辑与:指的是“两 个都是 1”时,运算结果为 1,其他情况下运算结果都为 0 的运算
- 逻辑或:指的是“至少有一方是 1”时,运算结果为 1,其他情况下运算结果都是 0 的 运算 。
- 逻辑异: 或指的是排斥相同数值的运算。“两个数值不同”,也就是说,当“其中一方是 1,另一方是 0”时运算结果是 1,其 他情况下结果都是 0。
计算器小数运算
实际上,依然存在程序运行后无法得到正确数值的情况。其中,小数运算就是一个典型的例子
0.1 累加 100 次 得到的不是10, 而是 10.00002
二进制数小数点前面部分的位权,第 1 位是 2 的 0 次幂、第 2 位 是 2 的 1 次幂......以此类推。小数点后面部分的位权,第 1 位是 2 的-1 次幂、第 2 位是 2 的-2 次幂,以此类推。
出错的原因
有一些十进制数的小数无法转换成二进制数”。例如,十进制数 0.1,就无法用二进制数正确表示,小数 点后面即使有几百位也无法表示。
实际上,十进制数 0.1 转换成二进制后,会变成 0.00011001100...(1100 循环)这样的循环小数
因为无法正确表示的数值,最后都变成了近似值
计算机这个功能有限的机器设备,是无法处理无限循环的小数的。计算机就会根据变量数据类型所对应的长度将数值 从中间截断或者四舍五入。
将 0.3333...这样的循环小数从 中间截断会变成 0.333333,这时它的 3 倍是无法得出 1 的(结果是 0.999999),计算机运算出错的原因也是同样的道理。
浮点数
科学计数法
双精度浮点数类型用 64 位、 单精度浮点数类型用 32 位来表示全体小数
- 浮点数: 是指用符号、尾数、基数和指数这四部分来表示的小数. +/-m*n.pow(e) +/- 符号 m:尾数 n:基数 e: 指数
浮点数的表现方式有很多种,这里我们使用最为普遍的IEEEA标准。
双精度浮点数和单精度浮点数在表示同一个数值时使用的位数不同。
双精度浮点数能够表示的数值范围要大于单精度浮点数。
符号部分是指使用一个数据位来表示数值的符号。该数据位是 1 时表示负,为 0 时则表示“正或者 0”。数值的大小用尾数部分和指数部分来表示。
小数就是用“尾数部分 × 2 的指数部分次幂”这样的形式来表示的
双精度浮点数
float64 使用64个byte, 内存表示 xey
符号位 | y 指数部分 | x 尾数 |
---|---|---|
1 | 11 | 52 |
单精度浮点数
符号位 | y 指数部分 | x 尾数 |
---|---|---|
1 | 8 | 23 |
因为尾数部分和指数部分并不只 是单单存储着用整数表示的二进制数。 尾数部分用的是“将小数点前面 的值固定为 1 的正则表达式”,而 指数部分用的则是“EXCESS 系统表 现”。
正则表达式和 EXCESS 系统
正则表达式(尾数处理)
为了方便计算机处理,需要制定一个统一的规则
- 尾数部分使用正则表达式:
- 十进制数的浮点数应该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则。0.75 就是“0.75×10 的0次幂”。根据这个规则来表示小数的方式,就是正则表达式。
- 在二进制数中,我们使用的是“ 将小数点前面的值固定为1的正则表达式”。就是将二进制数表示的小数左移或右移(这里是逻辑移位。因为符号位是独立的)数次后, 整数部分的第1位变为1,第2位之后都变为0(这样是为了消除第2位以上的数位)。第1位的1在实际的数据中不保存。由于第1位必须是1, 因此,省略该部分后就 节省了一个数据位,从而也就可以表示更多的数据范围(虽不算太多)。
EXCESS 系统(指数部分)
使用这种方法主要是为了表示负数时不使用符号位。
EXCESS 系统: 通过将指数部分表示范围的中间值设为 0,使得负数不需要用符号来表示。
浮点数与0进行比较
func compare(a float64) bool { if a < 0.0001 && a > -0.0001 { //精度 return true } }
字节序
大端 小端
内存布局
指针地址 0000 0000
内存存的方式: 字节序 小->大
内存高低位
|---内存----- 小---------> 大----|
内存 |
---|
小 ---------> 大 |
低位 -------> 高位 |
数字高低位
一个数字
一个十六进制数字 | ||
---|---|---|
0x | 04303 | 0201 |
高位 | 低位 |
小端 : 数字低位放到内存低位. 这样方便数字下标取 大端 : 数字高位放到内存低位。 如: jvm
内存对齐
为什么对齐
指令集
x86, x64 区别, 还有那些指令集
80386 是美国英特尔公司开发的微处理器的产品名。“80386 以上”是指 80386、80486、奔腾等微处理器。
加解密
对称加密
aes
非对称加密
散列
md5
a==b 破解
因为当 if a==b 这种写法的时候
底层使用
for (i:0; i< len(a)+len(b); i++) { a[i] == b[i] //没个字节比较 }
所以将所有字母数字组合进行大量请求的时候有一个明显的峰值,那么这个分支的字母就是第一个字母,依次类推,最后得到密码。
所以为了防止这种情况的出现,服务端发送 p*q 这样的大值指数时,服务端计算性能开销是比较小的,客户端需要比服务端小号更多性能,防止服务器被暴力破解。
当 p*q 非常大的时候这个就是一个NP问题。
增加攻击者的成本,这也是一种防护。