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 11System.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