二进制
一、 基础认知
1. 位、比特、字节、字符、字符集、字符编码
位、比特
数据(或程序)存储的计算中,会以内存(运行时)或磁盘为载体存储,而这些载体通常是由以百万计的晶体管的硅半导体芯片上,每个晶体管都是一个电路的开关。这些数据在计算机中的表现形式为一个个“开关”,开为1,关为0。0和1被解释为二进制数学系统中的数,称为比特(bit,二进制数),1个开关也就是1位的二进制数。
所以位 == 比特(bit)
字节
- 字节(byte)是最小的存储单元。每个字节由8个比特(bit、位)构成,也就是一个8位的二进制数。
- 数据一般都被编码为字节序列,用来存储或传输
字符、字符集、字符编码
概念
- 字符依赖于字符集和字符编码,转换为二进制存储在内存中。
- 字符集,即字符的集合,是 人类的数字、语言、标记等 相对于 机器能识别的二进制 映射
- 字符集有很多种,常用的是Unicode
- 字符集规定了人类字符的到机器语言的对应关系,但是并未规定这些字符如何去存储、设备又应该以什么规则去读取才能读取和解析到正确的字符
- 字符编码 是对字符集的实现。
- 根据字符集,将字符转换为机器可识别的二进制(每种编码的转换规则和结果并不相同)
- 常用的UTF-8,它的优势是通用性、可变长度、可兼容ASIIC字符集
- 一个字符占用几个字节,取决于字符集以及字符编码规则
Unicode
- Unicode的编码通常有2个字节(可能不满),也有4个字节,比如东方文字、符号、imoji等。这里讲的多少个字节并不是在计算机中存储占用的内存空间,要想得到实际占用的空间字节数,还需要相应的字符编码格式,依赖编码格式生成的二进制数才可以确定这个字符到底占用多少个字节。
- 字符集只是一个集合,字符编码是根据一定规则将某个字符从字符集中找到它的对应二进制数,再将这个二进制数进行加工处理后,得到的就是实际存储的字节数组,那么就可以得知真实的字节数。
- ASIIC码因为都是最早创建的Latin字符以及一些简单的符号,都是只使用1个字节表示
- Unicode的编码即使是Latin字符也使用了2个字节,最少使用了2个字节
UTF-8
- 是一种编码方式,它基于Unicode字符集的编码实现方式,并兼容ASIIC字符集,这样的话UTF-8编码后的Latin字符(ASIIC字符集)所占用的字节数就可以为1个字节,这里讲的"占用",即实际使用的内存或存储空间。
- 因为世界上存在多种字符集,而各个字符集的编码规则又不同,会导致各种文件内容乱码的问题,这时就需要一个统一的字符编码格式,UTF-8就是一种使用最广泛的、统一的编码格式
- UTF-8编码后的结果是变长的,为1 - 4个字节,如Latin字符为1个字节,部分汉字为2个字节,部分汉字为3个字节,表情符号等为4个字节,这是它的优势,可以节省空间以及具备无限制的扩展性
- 它的编码过程依据于Unicode字符集中的该字符使用的字节数,并根据一定的规则处理它,具体规则如下:
- 单字节的,如Latin字符,将这个字节共8位的最高位上设置为0,后7位为unicode码(去除无用的高位0之后)
- 多字节的,假设为T个字节,那么这个字节序列从高位开始数,第1个字节的最高位开始直至T位,都设置为1,T+1位设置为0;第2个字节的前2位设置为为10;第3个字节的前2位设置为为10…以此类推。也就是说除了第1个字节的特殊处理,后面的字节的头两位都为10。这其中没有被设为1或者0或者10的位,从高位开始依次填入unicode码的值。如果有剩余没有填满,则补0。(以上均为
从高位开始数起
)
规则总结表
字节数 | Unicode 16进制范围 | Unicode 2进制范围 | UTF-8编码格式(2进制) |
---|---|---|---|
1 | 0000 0000 ~ 0000 007F | 0000 0000 ~ 0111 1111 | 0xxx xxxx |
2 | 0000 0080 ~ 0000 07FF | 1000 0000 ~ 0111 1111 1111 | 110x xxxx 10xx xxxx |
3 | 0000 0800 ~ 0000 FFFF | 1000 0000 0000 0000 ~ 1111 1111 1111 1111 | 1110 xxxx 10xx xxxx 10xx xxxx |
4 | 0001 0000 ~ 0010 FFFF | 1 0000 0000 0000 0000 ~ 1 0000 1111 1111 1111 1111 | 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx |
举例:“我”字,unicode 16 为\u6211
,2进制为110 0010 0001 0001
- 按照3个字节处理
- 高位开始到第3位为止,设为1,第4位设置为0
- 将高位开始数的第2个字节的前2位设为10,第3个字节的前2位也设置为10
- 然后将Unicode 2进制码,按照从高到低的顺序,填入还未设置的位中,不足的补0
则UTF-8编码为:1110 1100 1001 0000 1010 0010
- 红色:UTF-8规则格式
- 绿色:unicode码2进制
- 最后的橙色:补0
这样以同样的UTF-8编码去读取字节数组时,就会检查编码规则规定的位,确定该字符是几个字节,然后取出其中的unicode码部分,从unicode字符集中找到对应的字符,输出。
2. 二进制
- 二进制是机器语言,前面已经讲过,数据的存储在物理层面就是
开
和关
,对机器来说它把开关定义为1、0。人类定义比如前面讲到的“我”的UTF-8存储为1110 1100 1001 0000 1010 0010
,人类让机器以UTF-8编码去处理这串二进制,存入或取到的就是汉字“我”。 - 二进制指的是,每个位上,逢2就进1到高一位上,同时原来的位设置为0;就像我们计算10进制加减法:9 + 1 = 10,二进制就是:1+1 = 10。(**注意:**这里的结果10,都读作“一零”,而不是“十”,这样才容易理解)
- 二进制相关的有很多规则,有一些是基础数学、有一些是和编程语言相关的计算方法,下面简单总结一下。
二进制和十进制的相互转换
十转二
拿一个十进制数,除2,得到一个商和余数,记录这个余数(只可能为1或者0);
再次拿上次得到的商,除2,得到余数,记录
…重复操作
直到商为0为止。
然后将得到的余数,倒序排列,就可以得到这个十进制数的二进制数
例子:十进制 19
19/2=9…1
9/2=4 …1
4/2=2 …0
2/2=1 …0
1/2=0 …1
余数顺序为11001,倒序后,那么二进制数就是10011(如果这里的19指是java中的int,则它的二进制数实际存储时是4个字节,高位全部补0:0000 0000 0000 0000 0000 0000 0001 0011
)
二转十
二进制转十进制基本算法更简单,即:
每一位上都是 2的(x位 - 1)次方 * 位上的二进制数
,然后将这些结果全部相加
例子:10011
2的4次方 * 1 + 2的3次方 * 0 + 2的2次方 * 0 + 2的1次方 * 1 + 2的0次方 * 1 = 16 + 0 + 0 + 2 + 1 = 19
二进制加减
加
原则:从低位向高位,逐位相加,某一位上:1+0 等于 1,1+1 等于10
以十进制 19+10为例:
19 的二进制: 10011
10 的二进制: 1010
则相加结果为:11101,即十进制的29
减
原则:被减数视为负数,转为 减数+(-被减数);负数转为二进制,需要先将其绝对值转为二进制数,然后在高位补1(或改成1),如果是负数则需要做反码,再做补码(正数的原码=反码=补码),两数相加,得到的值,这个值只取所属类型所占的字节数
- 反码:0变1,1遍0,但是最高位不变
- 补码:整个二进制数+1(不是每一位+1)
以十进制 19-10为例,假设这两个数都只占1个字节,是byte类型(-128 ~127):
- 19 的二进制(原、反、补): 0001 0011
- 10的二进制(原):0000 1010
- -10的二进制(原):1000 1010
- -10的二进制(反):1111 0101
- -10的二进制(补):1111 0110
- 结果:19 的二进制 + -10的二进制(补):1 0000 1001,取1个字节为:000 1001 ,即十进制的9
一个负的二进制数,需要转为十进制时,要先将其转为正数的二进制,这时需要按照之前的操作反向做一遍即可:补码 --> 反码–> 源码 --> 符号位归零 --> 转为10进制 --> 加上符号
二进制运算符(位运算)
二进制的运算,都是关于位的运算,将转化为二进制数,逐位(从低到高)对应运算
&(与)
两个数的对应二进制位,均为1,则结果的二进制位也为1;否则,结果的二进制位为0
|(或)
两个数的对应二进制位,只要有一个为1,则结果的二进制位为1;否则,结果的二进制位为0。
也可以理解为,两个均为0,结果才为0;否则为1。
^(异或)
两个数的对应二进制位,只要不相同(0、1或者1、0),则结果的二进制位为1;否则(1、1或0、0),结果的二进制位为0。
~(非)
二进制数的每一位,0变1,1遍0
<<(左移)
左移n位,低位补零。a << n 相当于 a*2的n次方。
>>(右移)
右移n位,最高位符号位不变,除最高位外的高位补零,。a >> n 相当于 a/2的n次方,结果取整,无余数。
>>>(无符号右移)
正数:这>>(右移)相同
负数:右移n位,最高位符号位也跟随移动,然后在高位补零。结果不可控,会转化为一个未知的正数。
拓展转载:开发中各种状态类型太多怎么办?
参考:十六进制的状态管理实战