Java移位操作符及其对char、byte、short类型数值的处理

来源:互联网 发布:奖金计算软件 编辑:程序博客网 时间:2024/06/05 04:57

1 二进制

1.1 二进制的表示

我们知道,计算机中所有数据都是以二进制形式存储。例如1(int)在二进制中的表现形式就是
00000000 00000000 00000000 00000001。
而0的二进制就是所有位上均为0。
具体的根据不同的编程语言,可能对于基础数据类型有不一样的字节数的定义。
对于Java而言,Java的八大数据类型的字节数定义如下:
这里写图片描述

1.2 正负数的定义

在二进制中,需要有一个bit来表示是数据的正负,这bit就是数据的最高位。 1表示负数,0表示正数。

1.3 最大值和最小值

以int数据类型为例
例如,Java中int类型的5在计算机中的表达就是
0000 0000 0000 0000 0000 0000 0000 0101。既然数据类型有字节数的限制,那么必然就会有该数据类型能够表达的范围。int类型的数据有32bits,因此最多能够表达的数字的个数就是2^32。

如果不考虑正负,那么32bits的数据能够表达的数据范围就是
00000…….000000(32个0)~11111……..1111111(32个1),也就是0到2^32-1。
但是1.2节讲到,数据有正负,因此在数据的表达范围就发生了变化。其真正的表达范围应该是
10000……000000(31个0)~011111……1111111(31个1)。也就是从 -2^31到2^31-1

为什么不是从00000……000000到1111111……1111111呢?

1.2中已经说过,最高位的1代表负数,0代表正数。因为000000……000000000代表0,最高位的0已经被占据了一位,那么剩下的31个bit能够表达的的数字也就是从0000……00000(31个0)到111111……11111(31个1),所以能够表达的就是从0到2^31-1。同理,对于负数的范围就是1000000……00000000到1111111……111111111,因此能够表达的就是从 -2^31到-1。

2 移位操作符

《Thinking in Java》中写到:移位操作符操作的对象也是二进制的’位’。移位操作符只可用来处理整数类型

2.1 左移位操作符

左移位操作符(<<)能按照操作符右侧指定的位数将操作符左侧的操作数向左移动(在低位补0)
例如,5<<2表示5向左移动两位
5的二进制表示就是101,那么左移两位之后,就是10100,也就是乘以4即等于20。

2.2 “有符号”右移位操作符

“有符号”右移位操作符(>>)则按照操作符右侧指定的位数将操作符左边的操作数向右移动。“有符号”右移位操作符使用“符号扩展”:若符号为正,则在高位插入0;若符号为负,则在高位插入1。也就是,移位之后,正数依然是正数,负数依然是负数。

2.1 “无符号”右移位操作符

Java中增加了一种“无符号”右移位操作符(>>>),它使用“零扩展”:无论正负,都在高位插入0。这一操作符是C或C++所没有的。

3 移位操作符的疑难点

3.1 对于【取待移动位数(二进制形式)右端的低5位作为实际移动位数】的理解

《Thinking in Java》中写到:如果对char、byte 或者 short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型。只有数值右端的低5位才有用。这样可以防止我们移位超过int型值所具有的位数。(译注:因为2的5次方为32,而int型值只有32位)若对一个long类型的数值进行处理,最后得到的结果也是long。此时只会用到数值右端的低6位,以防止移位超过long型数值具有的位数。

对上面这段话的理解是:移位操作符操作的运算对象是二进制的“位”,int类型是32位也就是2的5次幂 !如果移32位以上,那么原来的数的信息会全部丢失,这样也就没有什么意义了!所以上面的“只有右端的低5位才会有用”说的是:移位操作符右端的那个数(转换为二进制)的低5位才有用,即 X < < y; 是指y的低5位才有用,从而保证移动位数不能大于32。而对于long型也是同样的道理!

因此,如果对一个int 型,进行移位,X<< y; 当y小于32时,移位后的结果一般都在我们的预料当中;而如果y大于32时,由于移位超出了int所能表示的范围,这时就先把y化成二进制数,然后取该二进制数右端的低5位,再把这5位化成十进制,此时的这个十进制就是要对X移动的位数。

int a = 140;   System.out.println(Integer.toBinaryString(a << 34));/*    输出结果:    1000110000*/


上面那两个语句的执行过程是:执行语句 a << 34 对a左移34位时,先把a转换为二进制:10001100,
再把要移动的位数34转换为二进制:10 0010,对该二进制数取右端低5位,即00010,十进制数为2,
所以实际上是对a左移两位。
总结:设被移位的int型数值为x,要移动的位数为n,则x实际移动的位数为:n % 32

3.2 “无符号”右移位操作符结合赋值操作对byte、short类型数值的处理

《Thinking in Java》中写到:在进行“无符号”右移位结合赋值操作时,可能会遇到一个问题:
如果对byte或short值进行这样的移位运算,得到的可能不是正确的结果。它们会被先转换成int类型,
再进行右移操作,然后被截断,赋值给原来的类型,在这种情况下可能得到-1的结果。
以下代码可以证明此现象:

short a = -1;   System.out.println("short a = "+a);System.out.println(Integer.toBinaryString(a));System.out.println("a>>>2");System.out.println(Integer.toBinaryString(a>>>2));a >>>= 2;  //a = (short)(a>>>2);System.out.println("a >>>= 2");System.out.println(Integer.toBinaryString(a));byte b = -1;System.out.println("byte b = "+b);System.out.println(Integer.toBinaryString(b));System.out.println("b>>>2");System.out.println(Integer.toBinaryString(b>>>2));b >>>= 2;  //b = (byte)(b>>>2);System.out.println("b >>>= 2");System.out.println(Integer.toBinaryString(b));/*    输出结果:    short a = -1    11111111111111111111111111111111    a>>>2    111111111111111111111111111111    a >>>= 2    11111111111111111111111111111111    byte b = -1    11111111111111111111111111111111    b>>>2    11111111111111111111111111111111    b >>>= 2    11111111111111111111111111111111*/


分析:

  • 已知Java中short类型占16位,取值范围为 -2^15 ~ 2^15 -1,则short类型-1用二进制表示应为
    1111 1111 1111 1111;
  • Java中int类型占32位,取值范围为 -2^31 ~ 2^31-1,则int类型-1用二进制表示应为
    1111 1111 1111 1111 1111 1111 1111 1111;
  • Java中Integer类中toBinaryString(int i) 方法的源码为:

    public static String toBinaryString(int i) {    return toUnsignedString0(i, 1);{

  • short a = -1;
    System.out.println(Integer.toBinaryString(a));
    该段代码的执行过程为:先将short类型的-1自动转换为int类型的-1,再将int类型的-1的二进制输出

  • System.out.println(Integer.toBinaryString(a >>> 2));
    该段代码的执行过程为:先将short类型的-1自动转换为int类型的-1,二进制形式的变化就是
    1111 1111 1111 1111 –> 1111 1111 1111 1111 1111 1111 1111 1111,
    然后在向右移2位,得 1111 1111 1111 1111 1111 1111 1111 11

  • System.out.println(Integer.toBinaryString(a >>>= 2));
    该段代码的执行过程为:先将short类型的-1自动转换为int类型的-1,然后在向右移2位,运算过程为
    1111 1111 1111 1111 1111 1111 1111 1111 –> 0011 1111 1111 1111 1111 1111 1111 1111 ,
    最后需要将右侧运算结果赋值给左侧short型变量a,a >>>= 2; 实际为 short a = (short) a>>>2;
    将32位的int类型数值强制转换为16位的short类型数值,转换方式为:取int类型数值的右端低16位
    0011 1111 1111 1111 1111 1111 1111 1111 –> 1111 1111 1111 1111 此处:a为-1
    实际上这种特殊的现象为强制类型转换时的精度缺失。对于byte类型的也是如此。

  • 那么再想一下,要移动多少位才能不出现此现象呢?答案:大于等于17位。以“无符号”右移17位为例:
    shor a = -1; a >>>17 得
    1111 1111 1111 1111 1111 1111 1111 1111 –> 0000 0000 0000 0000 0111 1111 1111 1111 ,
    再将运算得到的int类型的结果赋值给short类型的变量a,
    0000 0000 0000 0000 0111 1111 1111 1111 –> 0111 1111 1111 1111 此处:a = 32767

3.3 “无符号”右移位操作符结合赋值操作对char类型数值的处理

虽然同样涉及到了强制类型的转换,但是对char类型数值的操作和对byte、short类型的操作不一样。
因为Java中char类型数值占16位,其取值范围为Unicode 0 ~ Unicode 2^16-1,
由此可以推测,char类型的数值使用Unicode编码,恒大于等于0,所以其计算方式不需要符号位的标识。
示例代码:

char c = (char)-1;System.out.println("char c = "+(int)c);System.out.println(Integer.toBinaryString(c));System.out.println("c>>>2");System.out.println(Integer.toBinaryString(c>>>2));c >>>= 2;System.out.println("c >>>= 2");System.out.println(Integer.toBinaryString(c));System.out.println((int)c);/*    输出结果:    char c = 65535    1111111111111111    c>>>2    11111111111111    c >>>= 2    11111111111111    16383*/


分析:

  • char c = (char)-1;
    -1默认为int类型,现要强制转换为char类型,其过程为:
    1111 1111 1111 1111 1111 1111 1111 1111 –> 1111 1111 1111 1111
    因Unicode编码格式不需要符号位,所以 1111 1111 1111 1111对应的char类型的值为
    2^16-1 = 65535;

  • c >>>= 2;
    先将char类型的数值转换为int类型,已知char类型的数值为65535,int类型的二进制表示为:
    0000 0000 0000 0000 1111 1111 1111 1111
    再向右移动两位,其过程为
    0000 0000 0000 0000 1111 1111 1111 1111 –> 0000 0000 0000 0000 0011 1111 1111 1111
    最后将int类型的运算结果赋值给char类型的变量c
    0000 0000 0000 0000 0011 1111 1111 1111 –> 0011 1111 1111 1111 此处 c = 16383

若对于编码及进制运算疑惑的同学,可参考以下文章:
http://blog.csdn.net/ldanduo/article/details/8203532/
http://blog.csdn.net/vickyway/article/details/48788769

PS:这是赵同学在CSDN中写的第一篇博客,希望可以帮助到其他同学,若有不对之处,望指正。
2017-12-08

阅读全文
1 0