走进0与1的世界
引言
简单来说计算机就是由晶体管和电路板组成的电子设备,它只能处理由0和1组成的二进制元数据。在这种物理表现形式下,设定2为基数,数位使用2^n形式进行计数,进位规则是“逢二进一”,借位规则是“借一当二”,所以称为二进制。
什么是有符号数、无符号数?
二进制分为有符号和无符号二进制数:无符号二进制数均表示正数;有符号二进制数的最高位是符号位,其余位是数字位,符号位为1时,表示负数,为0时表示正数。
采用余数思想处理数据“溢出”
Java中都是有符号二进制数。例如:int类型的数字,表示占4个字节(每个字节8个bit)的32bit的二进制数。根据数学中的排列组合,一个32位的2进制数,所能代表的数字范围是【-2^n~2^(n-1)-1】超出这个范围时表示“溢出”,超过上限值叫做“上溢出”,超过下限值叫做“下溢出”。溢出的部分采用了数学中的余数思想和同余定理来处理。例如:int最大值2^(n-1)-1最高位是0,其余位都是1,如果再加个1,则发生上溢出,此时的二进制数为最高位为1,其余位为0,即int的最小值。因此“上溢出”后的值又开始从最小到最大周而复始的变化,这说明数据“溢出”时的处理方式正是余数思想,其中取余的除数为最大值减去最小值+1,即[(2^(n-1)-1) - (-2^(n-1))+1 =2^(n-1) +1]。
二进制的原码、反码、补码的意义?
由于计算机在设计之初只实现了加法器,因此如果想让负数也支持加法运算,或者说让计算机支持减法运算,得统一正数和负数的二进制计算形式。二进制数最终都是以补码形式进行加减计算的。正数的补码与原码、反码一样;负数的反码等于去除符号位,其余按位取反,负数的补码等于反码加一。例如:有符号二进制数10100010对应的十进制数是多少?(-94)
方法一:先求补码,再针对补码除符号位外,进行按位加权求和,再取反。
方法二:原码除符号位外,进行按位加权求和,减去(2^n-1+1)。
方法三:反码除符号位外,其余各位按位加权求和+1后,取反。
二进制的位(位移、按位逻辑)运算
位移运算:
<<:带符号左移,低位补0。
>>:带符号右移,符号位为1时,说明是负数,符号位不变,高位补1;符号位为0时,说明是正数,高位补0。
>>>:无符号右移,高位补0。
值得关注的是:
位移操作时二进制的位数超过了系统指定的位数时,会发生数据溢出,溢出的位数直接舍去;(比如:一个32位的二进制数正数,发生左移或右移1位时,左边和右边的第一位都会因溢出而直接舍去,然后再在低位和高位补0) 在未发生数据溢出时,<<n相当于乘以2^n倍,>>n近似相当于除以2^n倍。
位移运算仅作用于整数(32位)和长整数(64位)数上,假如在整型数上移动32位,无论是否带符号以及方向,均为本身。因为移动的位数是个mod 32的结果,4>>>1和4>>>33是一个结果。负数在无符号右移63位时,除了当前位为1,之前的位均为0,达到最小值1,如果>>>64,则为其原值本身。
按位逻辑运算:
按位与&:参与操作的位中必须全部为1,结果才是1,否则就是0。
按位或|:参与操作的位中只要有一个是1,结果就是1。
按位异或^:参与操作的位中必须不同,结果才为1,否则为0。(相等的两个数异或结果为0,任意一个数的二进制数异或0的结果都是这个数本身)
二进制按位操作的意义:直接操作内存,效率高。
位运算应用场景
IP地址与整数互换:在实际工作中会有统计IP出现次数的需求。通常会存储在HashMap中,以IP地址作为Map的Key,累计出现的次数作为Map的Value。但IP字符串所占的字节数会比其对应的整数(32bit)占用更多内存,因此将IP地址转化成整数值作为key,会大大减少HashMap的实际存储容量。
IP地址—>长整型数
*方法1:“5,3,12,1”的IP地址,由4段10进制整数构成,每段IP的取值范围是0-255(2^8)之间,由此可以把这个点分IP地址表示为一个32位二进制数,每个IP段对应这个32位二进制的高24位、高16位、高8位和低8位。对这个32位二进制数从低位到高位使用二进制加权求和。
点分IP—>32位二进制result = 1*2^0+1*2^10+1*2^11+1*2^16+1*2^17+1*2^24
=1+2^8*4+2^8*8+2^8*256+2^8*512+2^8*256*256+1*2^26
=84085761L。
方法2:将IP看成是一个2^8即256进制表示的数,每一位相当于对256取余的结果。
如果用10进制表示法,来表示一个数时
53121= 5*10^4+3*10^3+1*10^2+2*10^1+1*10^0,
那么根据10进制表示法推导256进制表示法
result = 5*(2^8)^3 + 3*(2^8)^2+12*(2^8)^1+1*(2^8)^0
=5*256^3+3*256^2+12*256^1+1*256^0
=83886080+196608+3072+1=84085761L
*方法3:将IP看做4段8位二进制数,进行了位移操作的结果。其中第一段IP算术左移了24位,第二段IP算术左移了16位,第三段IP算术左移了8位,第四段IP未进行位移操作(算术左移<<操作,在没有发生数据溢出时,<<n 相当于对当前10进制数扩大了2^n倍)。
result =5<<24+3<<16+12<<8+1
= 5*2^24+3*2^16+12*2^8+1
=84085761L
长整型数—>IP地址:(长整型在位移之前先转化为对应的点分32位二进制形式,即00000101.00000011.00001100.00000001)
1、将长整型数>>>24位,高位补0,即得第一段IP值。
2、将长整型数前8位&0即将高8位设为0,再>>>16,高位补0,即得第二段IP值3。
即 : 00000101.00000011.00001100.00000001 & 0x00FFFFFF —>
00000000.00000011.00001100.00000001 (高位设为0),然后再>>>16, —>
00000000.00000000.00000000.00000011(3)
3、将长整型数前16位&0即将高16位设为0,再>>>8,高位补0,即得第三段IP值。
4、将长整型数前24位&0即将高24位设为0,即得第四段IP值。
5、将所得的四段IP值,使用StringBuilder拼接成完整IP地址。
利用&操作将相应的位设0,再>>>操作十进制转二进制
位移法求10进制数(正数)对应2进制如果decimalSource为负数-25,则其二进制是其正数取补的结果 。
例如:25对应的二进制原码为 00011001,
反码:11100110(原码按位取反)
补码:11100111(反码+1)
-25(10)—>11100111(2)
奇偶数校验:偶数的二进制最后一位总是0,奇数的二进制数总是1。基于这个特点,让一个数&1操作,代替取余,进行奇偶数判断。1的二进制数最后一位是1,其余位均为0。因此任意一个奇数&1的结果为1,偶数&1结果为0。例如:(5&1 = 0101&0001=0001=1;2&1=0010&0001=0000=0)
两数相等判断:相等的两个数按位异或结果为0,即x^x=0。
两数交换:使用异或操作实现性能高效,减少临时变量的使用。
哈希取余:如果a%b,b=2^n。则a%b=a&(b-1)。
总结
计算机只能处理0或1的二进制数据,二进制数以补码的形式进行加减运算;位移运算只发生在整型和长整型的二进制数中。因为每种数据类型有各自的取值范围,因此在进行数据加减或位移运算时均会发生数据溢出,溢出时采用余数思想进行处理。
加减运算本质就是两数和,mod这个除数的结果,而除数就是当前数据类型最大值减去最小值+1;而移位运算移动的位数本质也是将当前数据类型长度作为除数(int为32),使用二进制要移动的位数mod这个除数的结果。
二进制数除了加减运算、位移运算外还支持按位逻辑运算(按位与&、按位或|、按位非^、按位取反~)。利用二进制位操作可:简化复杂哈希取余运算、实现奇偶数校验、两数相等判断以及IP与整数互换等功能。