摘要
移位运算符大家都不陌生,左移和右移呗,左移相当于原数乘以2,右移相当于原数除以2。但是你考虑过Java中char、byte和short在移位时会发生什么吗?知道Java中移位运算符分为几种吗?每种的使用方法是否确定了然于心呢?下面我就带领大家问候一下Java中移位运算符他大爷。
分类
在Java中数字的为有符号数,这就意味着数据类型所占字节的所有位的最高位要占有1位来表示数字的正负,0 表示正 ,1 表示负。其二进制形式为有符号的二进制补码,这也就说明了为什么byte类型的数据范围为:-128—127。而移位运算符就是操作数字的二进制位。
在Java中有左移(<<),有符号的右移(>>),无符号的右移(>>>)。
左移(<< )能按照操作符右侧所指定的位数将操作数的二进制位向左移动(低位补0)。
有符号的右移(>>)能按照操作符右侧所指定的位数将操作数的二进制位向右移动,其中最高位填充规则为:若原数为负,最高位补1;若原数为正,最高位补0。
无符号的右移(>>>)能按照操作数右侧所指定的位数将操作数的二进制位向右移动,不管其原数正负,最高位统一补0。(这就意味着负数会变成正数)。
为什么左移没有这样的待遇?
原因:左移是向左移动,只涉及到最低位的取值,而最低位取值无论是有符号或者无符号都是补0,所以Java中不存在<<<运算符!
Java中还可以将移位运算符与=连用,代表移位后赋值给原变量,对应的格式为:<<= 、>>= 、 >>>=。
实例
int 型数据的移位
int型在Java中统一为4个字节,也是我们比较常用的一种数据格式,所以此处先利用int型简单展示一下各数据的移位效果,加深理解。
为了方便,此处用十六进制的数(直观),比如我们用正数0xA,对应的十进制为10,二进制为1010(前向的一堆0包括符号0省略了),同时我们还会用-0xA来对比结果。
Integer.toBinaryString这是自带的库函数,它的思想也是对于正数前向0省略,而对于负数,则从最高位1开始到结尾全部显示,一会我们分析一下它的源码,非常简答明了!)
正数
package com.study.mengyi.operate;
/**
* 学习移位运算
* @ClassName MoveBit
* @Description
* @Author Meng Yi
* @Date 2017年7月15日 下午2:11:07
*/
public class MoveBit {
public static void main(String[] args){
int x = 0xA;
System.out.println("原数的十进制:"+x); //原数的十进制
System.out.println("原数的二进制:"+Integer.toBinaryString(x)); //原数的二进制
System.out.println("左移的十进制:"+(x<<1)); //左移的十进制
System.out.println("左移的二进制:"+Integer.toBinaryString(x<<1)); //左移的二进制
System.out.println("有符号右移的十进制:"+(x>>1)); //有符号右移的十进制
System.out.println("有符号右移的二进制:"+Integer.toBinaryString(x>>1)); //有符号右移的二进制
System.out.println("无符号右移的十进制:"+(x>>>1)); //无符号右移的十进制
System.out.println("无符号右移的二进制:"+Integer.toBinaryString(x>>>1)); //无符号右移的二进制
}
}
运行结果:
很明显可以看出正数的移位运算比较简答, 因为正数的补码与原码相同,所以按照常规的思维很好理解,并且可以看出正数的有符号右移与无符号右移结果无异。
负数
package com.study.mengyi.operate;
/**
* 学习移位运算
* @ClassName MoveBit
* @Description
* @Author Meng Yi
* @Date 2017年7月15日 下午2:11:07
*/
public class MoveBit {
public static void main(String[] args){
int x = -0xA;
System.out.println("原数的十进制:"+x); //原数的十进制
System.out.println("原数的二进制:"+Integer.toBinaryString(x)); //原数的二进制
System.out.println("左移的十进制:"+(x<<1)); //左移的十进制
System.out.println("左移的二进制:"+Integer.toBinaryString(x<<1)); //左移的二进制
System.out.println("有符号右移的十进制:"+(x>>1)); //有符号右移的十进制
System.out.println("有符号右移的二进制:"+Integer.toBinaryString(x>>1)); //有符号右移的二进制
System.out.println("无符号右移的十进制:"+(x>>>1)); //无符号右移的十进制
System.out.println("无符号右移的二进制:"+Integer.toBinaryString(x>>>1)); //无符号右移的二进制
}
}
运行结果:
可以看出负数的二进制补码形式,然后无符号右移与有符号右移有重大的差别,无符号右移不仅较大的改变了数的大小,同时也变成了正数。到这里你应该很清晰这个三个运算符了吧。
byte、char、short移位运算
byte、char、short移位运算先把类型转换成int型,再进行移位,且结果为int型。并且在移位运算中, 移动的位数为右操作数的低5位(右操作数决定移动的位数),因为int型数为32位,其中一位是符号位,Java不允许一次性移位左操作数的所有位,也就是右操作数不能大于32。右操作数的低5位,5位二进制所代表的最大值为2^5-1,为31,所以取右操作数的低5位,就是只看右操作数的二进制的低5位,相当于右操作数对32取模的结果!相同的道理,long型就是右操作数的低6位有效。
吐槽《Java编程思想第四版》中文版对这一部分的描述,说int类型的值的移位,只有数值有端的低5位有效。我看到这里直接懵逼,啥意思啊,看了英文才恍然大悟,
Only the five low-order bits of the right-hand side will be used.
人家说的是右操作数啊,作者翻译成了数值右端,所以有时候碰到不理解的地方,直接上英文版!
package com.study.mengyi.operate;
/**
* byte short char 的移位运算
* @ClassName CharByteShortMoveBit
* @Description
* @Author Meng Yi
* @Date 2017年7月15日 下午4:29:27
*/
public class CharByteShortMoveBit {
public static void main(String[] args){
int x = -1;
System.out.println("-----int 移位运算并赋值---------");
x<<=1;
System.out.println("左移的十进制:"+x); //左移的十进制
System.out.println("左移的二进制:"+Integer.toBinaryString(x)); //左移的二进制
x = -1;
x>>=1;
System.out.println("有符号右移的十进制:"+x); //有符号右移的十进制
System.out.println("有符号右移的二进制:"+Integer.toBinaryString(x)); //有符号右移的二进制
x = -1;
x>>>=1;
System.out.println("无符号右移的十进制:"+x); //无符号右移的十进制
System.out.println("无符号右移的二进制:"+Integer.toBinaryString(x)); //无符号右移的二进制
byte b = -1;
System.out.println("-----byte 移位运算并赋值---------");
b<<=1;
System.out.println("左移的十进制:"+b); //左移的十进制
System.out.println("左移的二进制:"+Integer.toBinaryString(b)); //左移的二进制
b = -1;
b>>=1;
System.out.println("有符号右移的十进制:"+b); //有符号右移的十进制
System.out.println("有符号右移的二进制:"+Integer.toBinaryString(b)); //有符号右移的二进制
b = -1;
b>>>=1;
System.out.println("无符号右移的十进制:"+b); //无符号右移的十进制
System.out.println("无符号右移的二进制:"+Integer.toBinaryString(b)); //无符号右移的二进制
b = -1;
System.out.println("-----byte 仅移位运算---------");
System.out.println("无符号右移的十进制:"+(b>>>1)); //无符号右移的十进制
System.out.println("无符号右移的二进制:"+Integer.toBinaryString(b>>>1)); //无符号右移的二进制
}
}
你觉得答案是多少呢?你觉得x>>>=1
与b>>>=1
结果会不会一样呢?一分钟思考时间。之后在看我给的答案。
运行结果为:
-----int 移位运算并赋值---------
左移的十进制:-2
左移的二进制:11111111111111111111111111111110
有符号右移的十进制:-1
有符号右移的二进制:11111111111111111111111111111111
无符号右移的十进制:2147483647
无符号右移的二进制:1111111111111111111111111111111
-----byte 移位运算并赋值---------
左移的十进制:-2
左移的二进制:11111111111111111111111111111110
有符号右移的十进制:-1
有符号右移的二进制:11111111111111111111111111111111
无符号右移的十进制:-1
无符号右移的二进制:11111111111111111111111111111111
-----byte 仅移位运算---------
无符号右移的十进制:2147483647
无符号右移的二进制:1111111111111111111111111111111
上面答案可以从运行结果看出来是不一样的,b=-1,无符号右移后竟然还是-1?
原因:byte、short值进行移位运算并赋值的时候,会转换成int型,移位后进行赋值时就会截取低8位(byte)或者低16位(short)赋值给原变量,这样符号位也是由截取的位数的最高位决定的,比如上例子中,-1无符号右移后应该为01111111111111111111111111111111,对应的十进制为2147483647,但是再赋值给原byte类型的变量时,因为byte占一个字节,所以会截取低8位赋值给b,截取的结果为11111111(十进制为-1),所以b 输出为 -1,用Integer.toBinaryString(b)就会按int型的-1打印出来,11111111111111111111111111111111。
我又举了byte 仅移位运算例子来进行对比,我移位后只进行了输出并没有赋值,所以还是按int型结果输出,所以结果与int -1右移无异。
Integer.toBinaryString()
最后看一下Integer.toBinaryString()源码,看一下为什么符号位如果是0的时候,之后的0全部省略直到第一个有效位为1,为什么符号位为1的时候,则位数全部显示呢?
public static String toBinaryString(int i) {
return toUnsignedString(i, 1);
}
private static String toUnsignedString(int i, int shift) {
char[] buf = new char[32]; //int型占4个字节,所以是32位,数组的每一位对应二进制的每一位
int charPos = 32;
int radix = 1 << shift; //基数,这里radix基数为2,结合我们所学的左移,应该很好理解!
int mask = radix - 1;//基数减一的目的就是为了与每一位做与运算,保证正确获得到每一位,1&1=1,1&0=0
/*此处为什么用do-while呢?为了把0对应的二进位字串打印出来,因为do-while相比while至少执行一次,同时为什么此处倒着从数组开始赋值呢,个人认为此处是避免加一个索引导致每次都要判断是否到32了,所以这里巧妙的从数组尾部开始倒叙。
为什么符号位如果是0的时候,之后的0全部省略直到第一个有效位为1?
这里可以看到用到了无符号右移,当打印完有效位1的时候,由于整个数的值为0(00000)所以结束循环。这样就不显示前置一堆0。
为什么符号位为1的时候,则位数全部显示呢?
符号位为1的时候,从第一开始进行无符号的时候,1就是每存到数组一位数,自身就往右移一位,最高位补0,这样就会使数值i为正数,直到这个符号位存到数组后,才使i为0,结束循环*/
do {
buf[--charPos] = digits[i & mask];
i >>>= shift;
} while (i != 0);
return new String(buf, charPos, (32 - charPos));
}
为什么符号位如果是0的时候,之后的0全部省略直到第一个有效位为1?
这里可以看到用到了无符号右移,当打印完有效位1的时候,由于整个数的值为0(00000)所以结束循环。这样就不显示前置一堆0。
为什么符号位为1的时候,则位数全部显示呢?
符号位为1的时候,从第一开始进行无符号的时候,1就是每存到数组一位数,自身就往右移一位,最高位补0,这样就会使数值i为正数,直到这个符号位存到数组后,才使i为0,结束循环。