目录
1. Java虚拟机整数
在java虚拟机中整数有byte、short、int、long四种 分别表示 8位、16位、32位、64位有符号整数。
整数使用补码表示。
1.1. Java原码、补码、反码
1.1.1. 原码
原码就是符号位加上数字绝对值的二进制表示, 即用第一位表示符号(0正数 1负数), 其余位表示值, 比如以8位(一个字节)表示7的二进制:
[+7]原 = 00000111
[-7]原 = 10000111
原码是人脑最容易理解和计算的表示方式. 对于原码来说,绝对值相等的正数和负数只有符号位不同。
1.1.2. 反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+7] = [00000111]原 = [00000111]反
[-7] = [10000111]原 = [11111000]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.
1.1.3. 补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+7] = [00000111]原 = [00000111]反 = [00000111]补
[-7] = [10000111]原 = [11111000]反 = [11111001]补
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
1.1.4. 总结
正数:它的原码、反码、补码相同。
负数:
反码:在原码基础上符号位不变化,其余位数取反,
补码:负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
1.1.5. Java byte 类型的取值范围说明
最大值:0111 1111,即127
最小值:1000 0000 ,减去1是 1111 1111 按照位取反 1000 0000 得到-128
1.1.6. 常见问题
int a=232
//0000 0000 1110 1000
System.out.println(Integer.toBinaryString(a));
System.out.println((byte) a);
输出结果为-24:
1) 232补码(原码一样):0000 0000 0000 0000 0000 0000 1110 10000
2) 强转Byte(8位数字)后补码为 :1110 1000, 对应的数字原码 :先减1->1110 0111,再取反->1001 1000 为结果的原码即-24
1.1.7. 为何使用补码
现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:
[+1] = [00000001]原 = [00000001]反 = [00000001]补
所以不需要过多解释. 但是对于负数:
[-1] = [10000001]原 = [11111110]反 = [11111111]补
可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?
首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对值区域的加减. 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.
于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:
计算十进制的表达式: 1-1=0
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.
为了解决原码做减法的问题, 出现了反码:
计算十进制的表达式: 1-1=0
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.
于是补码的出现, 解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原+ [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)
使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231,-231 -1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.
1.2. 位移操作
位移操作:(只针对 int类型的数据有效,java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数).也可以作用于以下类型,即 byte,short,char,long(当然,它们都是整数形式)。当为这四种类型是,JVM先把它们转换成int型再进行操作。
<< 左移
>> 右移
>>> 无符号右移
【注】:Java中不存在<<<
1.2.1. <<左移
m<<n的含义:把整数m表示的二进制数左移n位,高位(含符号位)移出n位都舍弃,低位补0. (此时将可能会出现正数变成负数的形式)
实例:
1) 3<<2剖析:
3二进制形式: 00000000 0000000000000000 00000011,
按照位移得到: 00000000 0000000000000000 00001100,即12.
2) 左移使整数变为负数:
10737418<<8
10737418二进制表示形式:
00000000 10100011 11010111 00001010,
位移得到
10100011 11010111 00001010 00000000,即为:-1546188288.
1.2.2. >>右移
m>>n的含义:把整数m表示的二进制数右移n位,m为正数,高位全部补0;m为负数,高位全部补1.
实 例:
1) 3>>2剖析:
3二进制形式: 00000000 00000000 0000000000000011,位移后,得到 00000000 00000000 0000000000000000,即为0.
2) -3>>2剖析:
-3二进制形式: 11111111 1111111111111111 11111101,
位移后得到 1111111111111111 11111111 11111111,即为-1.
以上:每个整数表示的二进制都是32位的,如果右移32位和右移0位的效果是一样的。依次类推,右移32的倍数位都一样。
备注:对于右移32位与右移0位是结果是一样的,我一直不能够理解。现在我只能理解为32比较特殊。相当于整体全移。与移0位相同。左移也是一样的。
1.2.3. >>> 无符号右移
m>>>n:整数m表示的二进制右移n位,不论正负数,高位都补零。
实例:
1) 3>>>2剖析:
3二进制形式:00000000 00000000 00000000 00000011,
位移得到: 00000000 00000000 00000000 00000000,即0.
2) -3>>>2剖析:
-3二进制形式: 11111111 1111111111111111 11111101,
位移后得到: 00111111 11111111 11111111 11111111,即为1073741823.
1.2.4. 位移操作n为负数说明
对于1.2.1,1.2.2,1.2.3,如果n为负数:这时JVM会先让n对32取模,变成一个绝对值小于32的负数,然后再加上32,直到 n 变成一个正数。
实例:
4<<-10
4的二进制形式:00000000 00000000 0000000000000100,-10对32取模再加上32,得到22,则4<<-10,即相当于4<<22,
此时位移,得到00000001 00000000 0000000000000000,得到的即为:16777216。
综上所述:
m<<n即在数字没有溢出的前提下,对于正数和负数,左移n位都相当于m乘以2的n次方.
m>>n即相当于m除以2的n次方,得到的为整数时,即为结果。如果结果为小数,此时会出现两种情况:(1)如果m为正数,得到的商会无条件的舍弃小数位;(2)如果m为负数,舍弃小数部分,然后把整数部分加+1得到位移后的值。
接下来在此说说位操作的好处,速度超快,这些都是底层的二进制机器操作指令。
比如:a*2,
1. jvm先为变量a分配空间;
2. 再进行a*2的操作;
3. 再把结果返回给相应的变量。
而a<<1,和a*2一样,它只需要一条指令即可,速度很快。当然前三种位移操作都是对2的倍数进行操作时可用。
1.3. 位运算
1.3.1. ~(按位非)
【解义】一元操作符 ,对该整数的二进制形式逐位取反。
~4:(一元操作符)
4的二进制形式为:00000000 00000000 0000000000000100,
逐位取反后得到: 11111111 11111111 11111111 11111011,即为-5.
1.3.2. | (按位或)
【解义】对两个整数的二进制形式逐位进行逻辑或运算,原理为:1|0=1,0|0=0,1|1=1,0|1=1 等。
4 |-5:
4的二进制形式为:00000000 00000000 00000000 00000100,
-5的二进制形式为:11111111 11111111 11111111 11111011,
逐位进行逻辑或运算: 11111111 1111111111111111 11111111,即得到-1.
1.3.3. &(按位与)
【解义】对两个整数的二进制形式逐位进行逻辑与 运算,原理:1&0=0,0&0=0,1&1=1;0&1=0等。
4&-5:
4的二进制形式为:00000000 00000000 00000000 00000100
-5的二进制形式为:11111111 11111111 11111111 11111011
逐位进行逻辑与运算:00000000 00000000 0000000000000000,即得到0.
1.3.4. ^ (按 位异或)
【解义】对两个整数的二进制形式逐位进行逻辑异或运算,原理:1^1=0,1^0=1,0^1=1,0^0=0.
4^-5:
4的二进制形式为:00000000 00000000 00000000 00000100,
-5的二进制形式为:11111111 11111111 11111111 11111011,
逐位进行逻辑异或运算:11111111 1111111111111111 11111111,即得到-1.
实际应用:可以把字节转换为整数,-64&0xFF=192,也可以用八进制的形式,-64&0377=192、
其实0xFF和0377都表示的是整数255、
实际应用:按位异或可以比较两个数字是否相等,它利用 1^1=0,0^0=0的原理。 20^20==0