# 基础概念
# 1️⃣ 机器码
- 我们知道无论是代码还是数值,在计算机中最后都转换成了二进制的形式存在,而一个数值在计算机中的二进制表示形式,就是这个数的机器码。
- 机器码是有符号位的,在计算机中用一个二进制数的最高位存放符号,正数为
0
,负数为1
。- 示例如下 (使用原码表示):
- 十进制的
+5
,计算机字长为8
位,其二进制是00000101
。 - 十进制的
-5
,计算机字长为8
位,其二进制是10000101
。 - 其中的
00000101
和10000101
就是机器码。
# 2️⃣ 真值
- 由于机器码的第一位是符号位,所以其形式值就不等于其真值的数值,也就是说
10000101
表示的是-5
而不是133
(其中10000101
的十进制是131
,前提是不算最高位为符号位),因此-5
才是机器码的真值。
# 3️⃣ 原码
- 原码是一种计算机中对数字的二进制的表示方法,原码表示法在数值前面增加一个符号位 (即:最高位为符号位),正数该位为
0
,负数该位为1
,其余位表示数值的大小。[+5] = [00000101](原码)
[-5] = [10000101](原码)
- 因为第一位是符号位,因此
8
位二进制的取值范围就是[1111 1111,0111 1111]
也就是[-127,127]
。
# 4️⃣ 反码
- 反码是数值储存的一种,但是由于补码更能有效表现数字在计算机中的形式,所以多数计算机一般都不采用反码表示数。
- 反码的表示方法如下:
- 正数的反码是其本身。
- 负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
[+5] = [00000101](原码) = [00000101](反码)
[-5] = [10000101](原码) = [11111010](反码)
# 5️⃣ 补码
- 在计算机系统中数值一律使用补码来表示和存储,原因在于使用补码,可以将符号位和数值域统一处理,同时加减法也可以统一处理。
- 补码的表示方法如下:
- 正数的补码是其本身。
- 负数的补码是在其原码的基础上,符号位不变,其余各位取反然后
+1
,即:在反码的基础上+1
。 [+5] = [00000101](原码) = [00000101](反码) = [00000101](补码)
[-5] = [10000101](原码) = [11111010](反码) = [11111011](补码)
# 6️⃣ 总结
计算机中的符号数有三种表示方法,即:
原码
、反码
、补码
,三种表示方法均有符号位和数值位两部分,符号位都是用0
表示为正数
,用1
表示为负数
,而数值位三种表示方法各不相同,而在计算机系统中,数值一律使用补码来表示和存储。
# 位运算
位移操作:只针对
int
类型的数据有效,在Java
中一个int
的长度始终是32
位,也就是4
个字节,它操作的都是该整数的二进制数,也可作用于以下类型即:byte
、short
、char
、long
它们都是整数形式,当为这四种类型时JVM
先把它们转换为int
类型再进行操作。
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为 1 时,结果才为 1 |
| | 或 | 两个位都为 0 时,结果才为 0 |
^ | 异或 | 两个位相同为 0,不同为 1 |
~ | 取反 | 所有位置 0 变 1,1 变 0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补 0 |
>> | 带符号右移 | 各二进位全部右移若干位,低位丢弃,高位补为符号位 |
>>> | 无符号右移 | 各二进位全部右移若干位,低位丢弃,高位补 0 |
&& | 逻辑与 | 左右表达式均为 true 时,运算最终结果才为 true |
|| | 逻辑非 | 左右表达式只要有一个为 true,运算最终结果就为 true |
package top.rem.rain; | |
/** | |
* @Author: LightRain | |
* @Description: 打印 Java 中 int 整型的在底层的 32 位信息 | |
* @DateTime: 2023-12-14 22:49 | |
* @Version:1.0 | |
**/ | |
public class BinaryUtils { | |
public static void print(int num) { | |
for (int i = 31; i >= 0; i--) { | |
System.out.print((num & (1 << i)) == 0 ? "0" : "1"); | |
} | |
System.out.println(); | |
} | |
} |
# 1️⃣ 左移 (<<)
m << n
的含义是把整数m
表示的二进制数左移n
位,高位移出n
位都舍弃,低位补0
(此时将会出现正数变成负数的可能),示例如下:5 << 2
:把十进制的数值5
左移两位,按照如下步骤计算。- 把
5
转为16
位的二进制机器码:00000000 00000000 00000000 00000101
- 按左移原理,将二进制数左移两位:
00000000 00000000 00000000 00010100
- 左移后的结果为
20
Test.java public class Test {
public static void main(String[] args) {
print(5);
print(5 << 2);
print(20);
/*
执行结果:
00000000000000000000000000000101
00000000000000000000000000010100
00000000000000000000000000010100
整体左移 2 位,高位丢弃,低位补 0
*/
}
}
- 把
5 << 29
:把十进制的数值5
左移29
位,按照如下步骤计算。- 把
5
转为16
位的二进制机器码:00000000 00000000 00000000 0000101
- 按左移原理,将二进制数左移
29
位:10100000 00000000 00000000 00000000
- 左移后高位是
1
,结果显然是负数
- 把
- 总结:
m << n
即在数字没有溢出的前提下,对于正数和负数,左移n
位都相当于m
乘以2
的n
次方。
# 2️⃣ 右移 (>>)
m >> n
的含义是把整数m
表示的二进制数右移n
位,m
为正数,高位全部补0
,m
为负数,高位全部补1
,示例如下:5 >> 2
:把十进制的数值5
右移两位,按照如下步骤计算- 把
5
转为16
位的二进制机器码:00000000 00000000 00000000 00000101
- 按右移原理,将二进制数左移两位:
00000000 00000000 00000000 00000001
- 右移后的结果为
1
Test.java public class Test {
public static void main(String[] args) {
print(5 >> 2);
// 将二进制转为十进制
int res = Integer.parseInt("00000000000000000000000000000001", 2);
System.out.println("结果:" + res);
/*
执行结果:
00000000000000000000000000000001
结果:1
*/
}
}
- 把
-5 >> 2
:把十进制的数值-5
右移两位,按照如下步骤计算- 把
-5
转为16
位的二进制机器码:11111111 11111111 11111111 11111011
- 按右移原理,将二进制数右移两位:
11111111 1111111 11111111 11111110
- 右移后结果为
-2
Test.java public class Test {
public static void main(String[] args) {
print(-5);
print(-5 >> 2);
print(-2);
/*
执行结果:
11111111111111111111111111111011
11111111111111111111111111111110
11111111111111111111111111111110
*/
}
}
- 把
- 总结:
m >> n
即相当于m
除以2
的n
次方,得到的为整数时,即为结果,如果结果为小数,此时会出现两种情况:- 如果
m
为正数,得到的商会无条件的舍弃小数位。 - 如果
m
为负数,舍弃小数部分然后把整数部分+1
得到位移后的值。
- 如果
# 3️⃣ 无符号右移 (>>>)
m >>> n
:整数m
表示的二进制右移n
位,不论正负数,高位都补0
,示例如下:5 >>> 2
:把十进制的数值5
右移两位,按照如下步骤计算- 把
5
转为16
位的二进制机器码:00000000 00000000 00000000 00000101
- 按右移原理,将二进制数左移两位:
00000000 00000000 00000000 00000001
- 右移后结果为
1
Test.java public class Test {
public static void main(String[] args) {
print(5);
print(5 >>> 2);
print(1);
/*
执行结果:
00000000000000000000000000000101
00000000000000000000000000000001
00000000000000000000000000000001
*/
}
}
- 把
-5 >>> 2
:把十进制的数值-5
右移两位,按照如下步骤计算- 把
-5
转为16
位的二进制机器码:11111111 11111111 11111111 11111011
- 按右移原理,将二进制数右移两位:
00111111 11111111 11111111 11111110
- 右移后结果为正数:
1073741822
Test.java public class Test {
public static void main(String[] args) {
print(-5);
print(-5 >>> 2);
// 将二进制转为十进制
int res = Integer.parseInt("00111111111111111111111111111110", 2);
System.out.println("结果:" + res);
/*
执行结果:
11111111111111111111111111111011
00111111111111111111111111111110
结果:1073741822
*/
}
}
- 把
# 4️⃣ 按位非操作 (~)
~
按位取反操作符,对每个二进制位的内容求反,即:0
变为1
,1
变为0
,示例如下:- 把
-5
转为16
位的二进制机器码:11111111 11111111 11111111 11111011
~(-5)
取反结果:00000000 00000000 00000000 00000100
- 转为十进制结果为
4
Test.java public class Test{
public static void main(String[] args) {
print(-5);
print(~(-5));
// 将二进制转为十进制
int res = Integer.parseInt("00000000000000000000000000000100", 2);
System.out.println("结果:" + res);
/*
执行结果:
11111111111111111111111111111011
00000000000000000000000000000100
结果:4
*/
}
}
- 把
# 5️⃣ 按位与操作 (&)
&
位与操作,对应的二进制位进行进行与操作,两个都为1
才为1
,其它情况均为0
,示例如下:1 & 0 = 0
0 & 0 = 0
1 & 1 = 1
0 & 1 = 0
- 示例:
-5 & 4
-5
的二进制为:11111111 11111111 11111111 11111011
4
的二进制为:00000000 00000000 00000000 00000100
- 逻辑与运行结果:
00000000 00000000 00000000 00000000
- 最终结果为
0
Test.java public class Test{
public static void main(String[] args) {
print(-5);
print(4);
print(-5 & 4);
/*
执行结果:
11111111111111111111111111111011
00000000000000000000000000000100
00000000000000000000000000000000
*/
}
}
# 6️⃣ 按位或操作 (|)
|
位或操作符,对应的二进制位进行或操作,两个都为0
才为0
,其它情况均为1
,示例如下:1 | 0 = 1
0 | 0 = 0
1 | 1 = 1
0 | 1 = 1
- 示例:
-5 | 4
-5
的二进制为:11111111 11111111 11111111 11111011
4
的二进制为:00000000 00000000 00000000 00000100
- 逻辑或运算结果:
11111111 11111111 11111111 11111111
- 最终结果为
-1
Test.java public class Test{
public static void main(String[] args) {
print(-5);
print(4);
print(-5 | 4);
/*
执行结果:
11111111111111111111111111111011
00000000000000000000000000000100
11111111111111111111111111111111
*/
}
}
- 利用或的原理我们可以把字节转换为整数,
-64&0xFF = 192
其中都0xFF
表示整数255
Test.java public class Test{
public static void main(String[] args) {
System.out.println(-64 & 0xFF);
System.out.println(0xFF);
/*
执行结果:
192
255
*/
}
}
# 7️⃣ 按位异或操作 (^)
^
异或操作符,相同位值为0
否则为1
,示例如下:1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
- 示例:
-5 ^ 4
-5
的二进制为:11111111 11111111 11111111 11111011
4
的二进制为:00000000 00000000 00000000 00000100
- 逻辑异或运算结果:
11111111 11111111 11111111 11111111
- 最终结果为
-1
Test.java public class Test{
public static void main(String[] args) {
print(-5);
print(4);
print(-5 ^ 4);
print(-1);
/*
执行结果:
11111111111111111111111111111011
00000000000000000000000000000100
11111111111111111111111111111111
11111111111111111111111111111111
*/
}
}
- 利用逻辑异或操作有个作用就是可以比较两个数值是否相等,即:利用
1 ^ 1 = 0
或0 ^ 0 = 0
的原理,如:5 ^ 5 == 0
Test.java public class Test{
public static void main(String[] args) {
System.out.println(1 ^ 1);
System.out.println(0 ^ 0);
System.out.println(5 ^ 5);
/*
执行结果:
0
0
0
*/
}
}
# 8️⃣ 总结
有了上面的分析,我们对
Java
的位运算有了比较全面的了解,那么我们的程序通过位运算又有什么优势呢?其实通过位运算确实会比我们直接的代码程序运算会快很多,因为位运算直接运算的是计算机底层的二进制机器码操作指令,而我们的代码程序运算最终也是要转成计算机可以识别的二进制操作指令才能完成,位运算可以理解为省去了中间转换的操作,处理器可以直接操作。我们会在某些源码中经常能看到位运算的代码,其实原理是一样的,处理器能够直接支持和处理。
static final int SHARED_SHIFT = 16; | |
static final int SHARED_UNIT = (1 << SHARED_SHIFT); | |
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; | |
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; |