位运算
位运算是计算机中的基础运算单元,Java为了提高程序的运行性能,便可直接通过位运算符进行计算操作。
Java中提供的位运算符由擦欧总书和位运算符所组成,可以实现对数值类型的二进制数进行运算(在Java中所有数据都是以二进制数据的****形式进行运算的),在位运算中提供有两类运算符:逻辑运算符(~、&、|、^)和移位运算符(>>、<<、>>>),如下所示:
逻辑运算符 | 描述 |
---|---|
& | 按位“与” |
| | 按位”或“ |
^ | 异或(相同为0,不同为1) |
~ | 取反 |
移位运算符 | 描述 |
---|---|
<< | 左移位 |
>> | 右移位 |
>>> | 无符号右移位 |
逻辑运算符可与等号“=”联合使用,以便于更加简便地运算和赋值,即**“&=”、“|=”、“^=”都是合法的**(但由于**“~”是一元操作符**,所以不能与等号“=”连用)。
移位运算符均可与等号“=”联合使用,即**“<<=”、“>>=”、“>>>=”都是合法的**。此时,操作数左边的值会移动由右边数值指定的位数,再将得到的结果赋值给左边的变量。
1、十进制转化为二级制
十进制数据转化为二进制数据的原则为数据除以2取余数,最后倒着排列,例如,25的二进制值为11001,如下:
- 25/2 = 12——余数1
- 12/2 = 6 ——余数0
- 6 /2 = 3 ——余数0
- 3 /2 = 1 ——余数1
- 1 /2 = 0 ——余数1
倒着排列余数便可得11001。但是值得注意的是,Java的int型数据为32位(4字节),所以实际最终的数据为00000000 00000000 00000000 0011001。
范例:
public class TestDemo {
public static void main(String[] args) {
int x = 13;
int y = 7;
System.out.println(x & y); // 5
System.out.println(x | y); // 15
System.out.println("x左移位后的计算结果:" + (x << 2)); // 52
System.out.println("原变量x执行移位后的结果:" + x); // 13 由此可得,移位后原来的值不变
}
}
/*
结果分析:
13 的二进制:00000000 00000000 00000000 00001101
7 的二进制:00000000 00000000 00000000 00000111
& 的结果:00000000 00000000 00000000 00000101 转换为十进制:5
| 的结果:00000000 00000000 00000000 00001111 转换为十进制:15
<< 2 的结果:00000000 00000000 00000000 00110100 转换为十进制:52
*/
2、右移位(>>)与无符号右移位(>>>)的区别
在计算机中,数值表示正负是通过+、-来表示的:0–正,1–负
- 有符号右移 >>(正数,高位补0;负数,高位补1)
-
正数:4 >> 2
4 的二进制数(原码、补码、反码都相同):00000000 00000000 00000000 000001004 >> 2(高位补0):00000000 00000000 00000000 00000001 转化为十进制:1
tips:x >> n 的结果就是 【x / (2^n)】
-
负数:-4 >> 2(高位补1)
-4 的 原码:10000000 00000000 00000000 00000100(负数–>最高位为1)
-4 的 补码:11111111 11111111 11111111 11111100
负数原码 --> 补码:保证符号位不变,其余位置取反加1(或者 从右往左第一个1右边不变,左边全部取反)
-4 >> 2(高位补1):11111111 11111111 11111111 1111111
转化为原码(保留符号位,按位取反加上1):10000000 00000000 00000000 00000001 转化为十进制:-1
- 无符号右移 >>> (不论正负,高位均补0)
-
正数:4 >>> 2
同 4 >> 2 的运算相同,结果为:1
-
负数:-4 >>> 2
-4 的 原码:10000000 00000000 00000000 00000100(负数–>最高位为1)-4 的 补码:11111111 11111111 11111111 11111100
-4 >>> 2(高位补0): 00111111 11111111 11111111 11111111 转化为十进制:1073741823
public class TestDemo {
public static void main(String[] args) {
int x = 4;
int y = -4;
System.out.println(x + "<< 2 : " + (x << 2)); // 16
System.out.println(x + ">>> 2 : " + (x >>> 2)); // 1
System.out.println(x + ">> 2 : " + (x >> 2)); // 1
System.out.println(y + ">>> 2 : " + (y >>> 2)); // 1073741823
}
}
3、移位运算符的一些疑难点
3.1 对于“取移动位数(二进制形式)右端的低5位作为实际移动位数”
《Java编程思想》这本书中提到:如果对char、byte 或者 short类型的数值进行移位处理,那么在移位进行之前,它们会被转换为int类型,并且得到的结果也是一个int类型。只有数值右端的低5位才有用。这样可以防止我们移位超过int型值所具有的位数。(译注:因为2的5次方为32,而int型值只有32位)若对一个long类型的数值进行处理,最后得到的结果也是long。此时只会用到数值右端的低6位,以防止移位超过long型数值具有的位数。
对于上面这段话,我个人的理解是:移位运算符的运算对象是基本数据类型中的单个“比特”(bit),即二进制数。而int类型是32位(2^5=32),若移位32位以上,那么原来的数会丢失其信息,显然没有什么意义。故移位运算符右端的数(转换为二进制)的低5位才有用。而对于long类型也是同理。例如,对于一个int型,x << y:
-
若 y < 32,移位后的结果显然在意料之内
-
若 y > 32,超过了int类型所能表示的范围,此时,先将y转换为二进制数并取该数的低5位(此低5位转换的十进制数就是要对x移动
的位数)
public class TestDemo {
public static void main(String[] args) {
int i = 2;
System.out.println("i的二进制数:" + Integer.toBinaryString(i));
System.out.println("i的十进制数:" + i);
System.out.println("i << 31 二进制数:" + Integer.toBinaryString(i << 31));
System.out.println("i << 31 十进制数:" + (i << 31));
System.out.println("i << 32 二进制数:" + Integer.toBinaryString(i << 32));
System.out.println("i << 32 十进制数:" + (i << 32));
System.out.println("i >> 33 二进制数:" + Integer.toBinaryString(i << 33));
System.out.println("i >> 33 十进制数:" + (i << 33));
}
}
/*
输出结果:
i的二进制数:10
i的十进制数:2
i << 31 二进制数:0
i << 31 十进制数:0
i << 32 二进制数:10
i << 32 十进制数:2
i >> 33 二进制数:100
i >> 33 十进制数:4
*/
上述例子,i << 33,对i左移33位时,先将i转换为二进制数:00000000 00000000 00000000 00000010,33的二进制数:00000000 00000000 00000000 00100001(取其右端低5位,即00001,转换为十进制数为1),故等价为i << 1,实际上是对i左移1位。
tips:被移位的int类型数值为x,x >> n(或x << n),则实际移动的位数为:n % 32。
3.2 “无符号”右移位操作符结合赋值操作对byte、short类型数值的移位运算
《Java编程思想》这本书中提到:“移位”可与“等号”(<<=或>>=或>>>=)组合使用。此时,操作符左边的值会移动由右边的值指定的位数,再将得到的结果赋给左边的变量。但在进行“无符号”右移位结合赋值操作时,可能会遇到一个问题:如果对byte或short值进行这样的移位运算,得到的可能不是正确的结果。它们会先被转换为int类型,再进行右移操作,然后被截断,赋值给原来的类型,在这种情况下可能得到-1的结果。
对于上面这段话,我个人的理解是(注:byte类型的分析同short类型):
-
short类型的>>>操作:
-
short类型值为-1,转换为二进制数:11111111 11111111
-
Integer.toBinaryString(s1)会把s1转为int类型,故打印结果为:11111111 11111111 11111111 11111111
-
Integer.toBinaryString(s1 >>> 10),根据原文可知,会将short类型的s2转换为int类型,再无符号右移10位
-
移位后得到:00000000 00111111 11111111 11111111
-
然后直接返回int类型,故打印结果为:111111 11111111 11111111
-
-
short类型的>>>=操作:
-
short类型值为-1,转换为二进制数:11111111 11111111
-
Integer.toBinaryString(s2)会把s1转为int类型,故打印结果为:11111111 11111111 11111111 11111111
-
执行s2 >>>= 10,根据原文可知,会将short类型的s2转换为int类型,再无符号右移10位
-
移位后得到:00000000 00111111 11111111 1111111(之后再截断出后16位赋给s2,得:11111111 11111111)
-
打印的时short类型的s2又被转换为int类型,故打印结果为:11111111 11111111 11111111 11111111
-
public class TestDemo {
public static void main(String[] args) {
short s1 = -1;
// 11111111 11111111 11111111 11111111
System.out.println("s1: " + Integer.toBinaryString(s1));
// 111111 11111111 11111111
System.out.println("s1 >>> 10: " + Integer.toBinaryString(s1 >>> 10));
short s2 = -1;
// 11111111 11111111 11111111 11111111
System.out.println("s2: " + Integer.toBinaryString(s2));
s2 >>>= 10;
// 11111111 11111111 11111111 11111111
System.out.println("s2 >>>= 10: " + Integer.toBinaryString(s2));
// 11111111 11111111 11111111 11111111
//System.out.println("s2 >>>= 10: " + Integer.toBinaryString(s2 >>>= 10));
}
}
/*
输出结果:
s1: 11111111111111111111111111111111
s1 >>> 10: 1111111111111111111111
s2: 11111111111111111111111111111111
s2 >>>= 10: 11111111111111111111111111111111
*/
tips:简而言之,short、byte类型在进行“>>>=”操作时,需截断赋值。那么移动几位才不出现此现象呢?答案便是:大于等于17位。
以“short a = -1; a >>>= 17”为例: 则有
11111111 11111111 11111111 11111111 ——> 00000000 00000000 01111111 11111111
再将运算得到的int类型的结果赋值给short类型的变量a
00000000 00000000 01111111 11111111 ——> 01111111 11111111
转换为十进制数为,即a = 32767。
public class TestDemo {
public static void main(String[] args) {
short b = -1;
System.out.println("b的二进制数: " + Integer.toBinaryString(b));
System.out.println("b >>>= 16: " + Integer.toBinaryString(b >>>= 16));
System.out.println("b的十进制数: " + b);
short a = -1;
System.out.println("a的二进制数: " + Integer.toBinaryString(a));
System.out.println("a >>>= 17: " + Integer.toBinaryString(a >>>= 17));
System.out.println("a的十进制数: " + a);
short c = -1;
System.out.println("a的二进制数: " + Integer.toBinaryString(a));
System.out.println("c >>>= 17: " + Integer.toBinaryString(c >>>= 18));
System.out.println("c的十进制数: " + c);
}
}
/*
输出结果:
b的二进制数: 11111111111111111111111111111111
b >>>= 16: 11111111111111111111111111111111
b的十进制数: -1
a的二进制数: 11111111111111111111111111111111
a >>>= 17: 111111111111111
a的十进制数: 32767
a的二进制数: 111111111111111
c >>>= 17: 11111111111111
c的十进制数: 16383
*/