程序是怎样跑起来--读书笔记

为什么80%的码农都做不了架构师?>>>   hot3.png

程序是怎样跑起来--读书笔记

需要重点了解的一些问题

  • 进制转换:二进制,八进制,十六进制
  • 补码, 原码, 反码
  • 位操作:左移,右移,与,非,否,异或
  • 浮点数: 科学计数法,单精度,双精度,与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 系统

正则表达式(尾数处理)

为了方便计算机处理,需要制定一个统一的规则

  • 尾数部分使用正则表达式:
  1. 十进制数的浮点数应该遵循“小数点前面是0,小数点后面第1位不能是0”这样的规则。0.75 就是“0.75×10 的0次幂”。根据这个规则来表示小数的方式,就是正则表达式。
  2. 在二进制数中,我们使用的是“ 将小数点前面的值固定为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问题。

增加攻击者的成本,这也是一种防护。

转载于:https://my.oschina.net/solate/blog/2998324

你可能感兴趣的:(程序是怎样跑起来--读书笔记)