你好,欢迎继续 Java 零基础学习!
在上一篇中,我们学习了变量、数据类型和运算符,知道了计算机内部所有数据最终都是以二进制 形式存储的。今天我们就深入这个"底层世界",彻底搞懂 进制转换 (二进制、八进制、十六进制)、位运算 (直接操作二进制位的运算符)以及 原码、反码、补码(整数在计算机中的编码方式)。掌握了这些,你就能理解很多底层原理,写出更高效的代码。
1. 为什么需要不同的进制?
我们日常生活中最熟悉的是十进制 (0-9,逢十进一),但计算机只认识二进制 (0 和 1,逢二进一)。二进制虽然简单,但对人来说读写太长,容易出错。于是引入了八进制 (0-7,逢八进一)和十六进制(0-9 和 A-F,逢十六进一)作为二进制的"缩写",方便程序员查看和书写。
例如,二进制 1010 1100 写成十六进制是 AC,简洁多了。
2. 进制转换
2.1 二进制、八进制、十六进制 → 十进制
方法 :按权展开求和。
所谓"权",就是每一位的"单位",从右向左依次是 基数^0、基数^1、基数^2......
示例 :二进制 1011 转十进制
- 从右向左:第0位:1×2⁰ = 1
- 第1位:1×2¹ = 2
- 第2位:0×2² = 0
- 第3位:1×2³ = 8
- 总和:1+2+0+8 = 11
示例 :十六进制 2F 转十进制
- 第0位:F(即15)×16⁰ = 15
- 第1位:2×16¹ = 32
- 总和:15+32 = 47
2.2 十进制 → 二进制、八进制、十六进制
方法 :除基取余法(整数部分)。
不断除以目标进制,取余数,直到商为 0,将余数从下往上排列。
示例:十进制 25 转二进制
- 25 ÷ 2 = 12 余 1
- 12 ÷ 2 = 6 余 0
- 6 ÷ 2 = 3 余 0
- 3 ÷ 2 = 1 余 1
- 1 ÷ 2 = 0 余 1
从下往上:11001,即 25₁₀ = 11001₂
示例:十进制 47 转十六进制
- 47 ÷ 16 = 2 余 15(即 F)
- 2 ÷ 16 = 0 余 2
从下往上:2F,即 47₁₀ = 2F₁₆
2.3 二进制 ↔ 八进制、十六进制
因为 2³ = 8,2⁴ = 16,所以可以每3位二进制对应1位八进制,每4位对应1位十六进制。
二进制 → 八进制 :从右向左每3位一组,不足补0,每组转成0-7的数字。
示例 :二进制 1011010 分组:1 011 010(从右向左:010、011、1→001),即 001 011 010 → 1 3 2(八进制 132)。
二进制 → 十六进制 :从右向左每4位一组,不足补0,每组转成0-9或A-F。
示例 :同上 1011010 分组:101 1010 → 0101 1010 → 5 A(十六进制 5A)。
反过来,八进制或十六进制每一位转成对应的3位或4位二进制即可。
3. 原码、反码、补码
在计算机中,整数是以补码 形式存储的。为什么要有这些编码?因为它们解决了 正负数表示 和 减法运算统一 的问题。
3.1 机器数与真值
- 机器数:一个数在计算机中的二进制表示,通常最高位为符号位(0 正,1 负)。
- 真值:带符号的实际数值。
例如,用 8 位表示:
+5 的机器数:0000 0101,真值 +5。
-5 的机器数:不能直接用 1000 0101 表示,因为这种"原码"会导致运算复杂。
对于 -5 这样的负数,其补码表示是通过以下步骤得到的(以 8 位为例): 1. 先写出 5 的二进制原码:0000 0101。 2. 求反码:将每一位取反(0 变 1,1 变 0),得到 1111 1010。 3. 反码加 1,得到补码:1111 1011。
因此,-5 在计算机中的机器数(8 位)就是 1111 1011。
之所以不用原码 1000 0101,是因为原码会导致加减运算复杂(需要判断符号位、处理正负),而补码可以将减法统一为加法(例如 5 - 5 可转为 5 + (-5),用补码计算正好得到 0),并且 0 的表示唯一,硬件实现简单。这就是现代计算机采用补码的原因。
3.2 原码
- 最高位为符号位,其余位表示绝对值。
- 例如 8 位原码:
- +5:
0000 0101 - -5:
1000 0101
- +5:
- 缺点 :0 有两种表示(
0000 0000和1000 0000),且加减运算复杂(需判断符号)。
3.3 反码
- 正数的反码 = 原码。
- 负数的反码 = 符号位不变,其余位按位取反。
- 例如 8 位反码:
- +5:
0000 0101 - -5:
1111 1010
- +5:
- 缺点 :0 仍有两种表示(
0000 0000和1111 1111),且加法可能需循环进位。
3.4 补码
- 正数的补码 = 原码。
- 负数的补码 = 反码 + 1。
- 例如 8 位补码:
- +5:
0000 0101 - -5:
1111 1011
- +5:
- 0 的补码只有一种:
0000 0000。 - 利用补码,可以将减法转化为加法,统一了加减运算。
为什么补码能统一加减?
因为对任意整数 a,其负数 -a 的补码等于 2ⁿ - a(n 为位数)。这样 a + (-a) = 2ⁿ,在 n 位范围内溢出后正好为 0。所以计算机只需加法器即可完成减法。
3.5 补码的取值范围(以 8 位为例)
- 最大正数:
0111 1111= 127 - 最小负数:
1000 0000= -128(因为 -128 的补码是 1000 0000,没有对应的原码和反码) - 范围:-128 ~ 127
3.6 已知补码求真值
- 若符号位为 0,直接转十进制。
- 若符号位为 1,则先减 1 得反码,再取反得原码(符号位不变),或者直接用公式:真值 = -(2ⁿ - 数值部分的值)。
例如补码1111 1011,数值部分(不含符号位)为 123(即 1111011₂),则真值 = -(256 - 123) = -133?不对,256-123=133,取负得 -133,但应该是 -5。错误。正确公式:真值 = -(2ⁿ - 补码表示的整数(当作无符号))。补码1111 1011当作无符号整数是 251,2⁸=256,256-251=5,所以真值 -5。或者按步骤:减1得反码1111 1010,取反得原码1000 0101,即 -5。
4. 位运算
位运算是直接对整数的二进制位进行操作的运算符,效率极高。Java 中位运算符适用于整数类型(byte, short, int, long, char)。
4.1 按位与 &
- 规则:两位都为 1 时结果为 1,否则为 0。
- 用途:清零指定位、取指定位。
示例:
java
int a = 5; // 二进制 0101
int b = 3; // 二进制 0011
int c = a & b; // 0001 = 1
4.2 按位或 |
- 规则:两位只要有一个为 1 结果为 1。
- 用途:置位(将某些位设为 1)。
示例:
java
int a = 5; // 0101
int b = 3; // 0011
int c = a | b; // 0111 = 7
4.3 按位异或 ^
- 规则:两位相同为 0,不同为 1。
- 性质:a ^ a = 0;a ^ 0 = a;异或可用于交换变量值(不用临时变量)。
示例:
java
int a = 5; // 0101
int b = 3; // 0011
int c = a ^ b; // 0110 = 6
4.4 按位取反 ~
- 规则:0 变 1,1 变 0(包括符号位)。
- 注意:
~a = -a - 1(因为补码特性)。
示例:
java
int a = 5; // 00000000 00000000 00000000 00000101
int b = ~a; // 11111111 11111111 11111111 11111010 = -6
4.5 左移 <<
- 规则:将二进制位全部左移若干位,高位丢弃,低位补 0。
- 效果:每左移一位,相当于乘以 2(在不溢出的情况下)。
示例:
java
int a = 5; // 0101
int b = a << 1; // 1010 = 10
int c = a << 2; // 10100 = 20
4.6 右移 >>(带符号右移)
- 规则:将二进制位全部右移若干位,低位丢弃,高位补符号位(正数补0,负数补1)。
- 效果:每右移一位,相当于除以 2 并向下取整(向负无穷方向)。
示例:
java
int a = 10; // 1010
int b = a >> 1; // 0101 = 5
int c = a >> 2; // 0010 = 2
int neg = -10; // 11111111 11111111 11111111 11110110 (补码)
int d = neg >> 1; // 11111111 11111111 11111111 11111011 = -5
4.7 无符号右移 >>>
- 规则:右移后,高位始终补 0,不管原来是正是负。
- 用途:对二进制位进行逻辑右移,不考虑符号。
示例:
java
int neg = -10;
int e = neg >>> 1; // 01111111 11111111 11111111 11111011 = 2147483643 (很大的正数)
5. 位运算的实际应用
5.1 判断奇偶性
用 (n & 1) == 1 判断奇数,比 n % 2 == 1 效率高(但现代编译器会优化,差别不大)。
java
int n = 5;
if ((n & 1) == 1) {
System.out.println("奇数");
}
5.2 交换两个数(不用临时变量)
java
int a = 3, b = 5;
a = a ^ b;
b = a ^ b; // b = (a ^ b) ^ b = a
a = a ^ b; // a = (a ^ b) ^ a = b
5.3 求 2 的幂次
左移 1 << n 得到 2ⁿ。
java
int power = 1 << 4; // 16
5.4 掩码操作
例如,取一个 int 的低 8 位:value & 0xFF。
5.5 权限管理
用二进制位表示权限(如读、写、执行),通过位运算设置和检查。
java
// 权限定义
int READ = 1 << 0; // 001
int WRITE = 1 << 1; // 010
int EXEC = 1 << 2; // 100
// 赋予权限:读+写
int perm = READ | WRITE; // 011
// 检查是否有读权限
if ((perm & READ) != 0) { ... }
6. 综合示例:进制与位运算
下面编写一个程序,演示进制转换、补码表示和位运算。
java
/**
* 演示进制转换、原码反码补码概念、位运算
*/
public class BitDemo {
public static void main(String[] args) {
// 进制转换:十进制转二进制、十六进制
int num = 47;
System.out.println(num + " 的二进制:" + Integer.toBinaryString(num));
System.out.println(num + " 的八进制:" + Integer.toOctalString(num));
System.out.println(num + " 的十六进制:" + Integer.toHexString(num));
// 补码表示(以 byte 为例,-5)
byte b = -5;
System.out.println("-5 的二进制补码:" + Integer.toBinaryString(b & 0xFF)); // 只取低8位
// 位运算示例
int a = 5; // 0101
int c = 3; // 0011
System.out.println("a & c = " + (a & c)); // 1
System.out.println("a | c = " + (a | c)); // 7
System.out.println("a ^ c = " + (a ^ c)); // 6
System.out.println("~a = " + (~a)); // -6
// 移位
System.out.println("a << 1 = " + (a << 1)); // 10
System.out.println("a >> 1 = " + (a >> 1)); // 2
System.out.println("-5 >> 1 = " + (-5 >> 1)); // -3
System.out.println("-5 >>> 1 = " + (-5 >>> 1)); // 2147483645
// 应用:判断奇偶
int n = 7;
System.out.println(n + " 是奇数? " + ((n & 1) == 1));
// 应用:交换变量
int x = 10, y = 20;
System.out.println("交换前:x=" + x + ", y=" + y);
x = x ^ y;
y = x ^ y;
x = x ^ y;
System.out.println("交换后:x=" + x + ", y=" + y);
}
}
运行结果:
7. 常见问题与提示
- 补码的 -128 没有原码和反码:8 位时,-128 的补码是 1000 0000,其原码和反码不存在(因为无法用 8 位表示 +128)。
- 位运算只适用于整数:不能用于浮点数、布尔值。
- 移位运算的位数超过类型位数时,会取模:例如对 int 左移 33 位相当于左移 33 % 32 = 1 位。
- 无符号右移 >>> 对于正数效果同 >>,对于负数会产生大正数。
8. 总结与下期预告
今天我们深入了计算机的底层世界:
- 进制转换:不同进制间的转换方法,特别是二进制与十六进制的便捷转换。
- 原码、反码、补码:理解了为什么整数用补码存储,以及如何转换。
- 位运算:7 种位运算符及其应用场景,如掩码、权限、高效计算。
这些知识虽然抽象,但它们是理解计算机工作原理的基础。下一篇文章我们将学习 流程控制语句(if、switch、循环),让程序拥有判断和重复执行的能力,真正写出有逻辑的程序。
动手实践:
- 练习十进制 100、255、-128 转二进制、八进制、十六进制。
- 写出 +7 和 -7 的 8 位原码、反码、补码。
- 用位运算实现一个程序,打印一个整数的二进制形式,并统计其中 1 的个数。
如果你在学习中遇到困惑,欢迎留言交流。我们下期见! 🚀
