问候Java移位运算符大爷

摘要

  移位运算符大家都不陌生,左移和右移呗,左移相当于原数乘以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>>>=1b>>>=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,结束循环。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值