进制、数据存储与字符编码基础 | JavaSE
进制、数据存储与字符编码基础
一、学习目标
完成本章后,你应该能够:
- 能够解释计算机为什么使用二进制表示和存储数据。
- 能够区分 bit、Byte 以及常见的数据容量单位。
- 能够完成二进制、八进制、十进制、十六进制之间的基础转换。
- 能够正确书写 Java 中的二进制、八进制和十六进制整数。
- 能够解释字符为什么可以用数字表示,并理解 ASCII、Unicode、UTF-8 之间的基本关系。
- 能够理解 Java
char与 Unicode 字符之间并不是绝对的一一对应关系。 - 能够根据进制和字符编码相关规则分析简单代码,并定位常见错误。
二、核心知识
2.1 为什么计算机使用二进制
我们平时使用的是十进制:
0 1 2 3 4 5 6 7 8 9
而计算机底层大量使用二进制:
0
1
原因并不是计算机“更喜欢 0 和 1”,而是数字电路天然适合表示两种稳定状态。
例如:
低电平 / 高电平
关闭 / 开启
无信号 / 有信号
这些状态可以抽象成:
0 / 1
因此,无论程序中保存的是:
18
'A'
"Hello"
一张图片
一段音乐
一个 Java 对象
最终都需要转换为可以由计算机存储和处理的二进制数据。
可以建立这样一条基本认识:
现实世界的信息
↓
程序中的数据
↓
数字表示
↓
二进制
↓
硬件存储和处理
2.2 bit 与 Byte
bit
bit(Binary Digit,比特)表示一个二进制位。
一个 bit 只能表示:
0
或者:
1
因此 bit 是最基本的二进制信息单位。
Byte
Byte(字节,B)由 8 个 bit 组成:
1 Byte = 8 bit
例如:
01100001
就是一个由 8 位二进制组成的字节。
Java 中:
byte
这种基本数据类型正好使用 8 位保存数据。
2.3 常见容量单位
初学阶段经常看到:
B
KB
MB
GB
TB
计算机课程中通常按照下面的方式理解:
1 KB = 1024 B
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
原因在于:
1024 = 2^10
所以它非常适合二进制计算体系。
严格来说,在现代标准中:
1 KiB = 1024 B
1 MiB = 1024 KiB
而 SI 单位中的:
1 kB = 1000 B
但在很多操作系统、教材和日常技术交流中,仍然经常将 KB 按照 1024 B 进行理解。
学习 JavaSE 时,首先理解二进制容量的递进关系即可。
2.4 什么是进制
进制本质上是一套表示数字的规则。
“几进制”可以理解为:
使用多少种基本数字,计数到多少时产生进位。
例如十进制:
0 1 2 3 4 5 6 7 8 9
逢十进一。
二进制:
0 1
逢二进一。
常见进制如下:
| 进制 | 基数 | 可以使用的数字 |
| -------- | ---: | -------------- |
| 二进制 | 2 | 0、1 |
| 八进制 | 8 | 0~7 |
| 十进制 | 10 | 0~9 |
| 十六进制 | 16 | 0~9、A~F |
十六进制中的:
A = 10
B = 11
C = 12
D = 13
E = 14
F = 15
大小写均可表示十六进制数字。
2.5 位权:理解进制转换的核心
一个数字真正的数值,不只由“这一位是什么”决定,还由“它处在哪一位”决定。
例如十进制:
123
实际上表示:
1 × 10²
+
2 × 10¹
+
3 × 10⁰
即:
100 + 20 + 3
二进制同样如此。
例如:
1101
从右向左对应:
2⁰ 2¹ 2² 2³
所以:
1101₂
=
1 × 2³
+
1 × 2²
+
0 × 2¹
+
1 × 2⁰
结果:
8 + 4 + 0 + 1 = 13
因此:
1101₂ = 13₁₀
这就是按权展开。
2.6 十进制转换为二进制
对于非负整数,可以使用经典的:
除二取余法。
例如将十进制 13 转换成二进制。
不断除以 2:
13 ÷ 2 = 6 余 1
6 ÷ 2 = 3 余 0
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
将余数倒着排列:
1101
所以:
13₁₀ = 1101₂
同理:
6₁₀ = 110₂
2.7 二进制与八进制
因为:
8 = 2³
所以每 3 个二进制位可以对应一个八进制位。
例如:
110 101
分别计算:
110₂ = 6
101₂ = 5
因此:
110101₂ = 65₈
这也是为什么八进制特别适合用来简化二进制表示。
2.8 二进制与十六进制
因为:
16 = 2⁴
所以每 4 个二进制位可以对应一个十六进制位。
例如:
1111 1010
分别计算:
1111₂ = 15 = F
1010₂ = 10 = A
因此:
11111010₂ = FA₁₆
这也是为什么开发过程中十六进制非常常见。
一个字节有 8 位:
11111111
刚好可以表示成两个十六进制数字:
FF
因此十六进制非常适合观察二进制数据。
三、使用方法
3.1 Java 中不同进制整数的写法
Java 支持直接书写二进制、八进制、十进制和十六进制整数。
public class NumberSystemDemo {
public static void main(String[] args) {
int binary = 0b0110_0001;
int octal = 0141;
int decimal = 97;
int hexadecimal = 0x61;
System.out.println(binary);
System.out.println(octal);
System.out.println(decimal);
System.out.println(hexadecimal);
}
}
四个变量保存的数值实际上完全相同:
97
97
97
97
对应关系如下:
0b01100001
↓
二进制
0141
↓
八进制
97
↓
十进制
0x61
↓
十六进制
Java 的整数进制前缀如下:
| 进制 | Java 写法 |
| -------- | ------------ |
| 二进制 | 0b 或 0B |
| 八进制 | 前导 0 |
| 十进制 | 无特殊前缀 |
| 十六进制 | 0x 或 0X |
3.2 使用下划线提高数字可读性
Java 允许在数字之间使用下划线:
int binary = 0b0110_0001;
int money = 1_000_000;
long population = 8_000_000_000L;
下划线不会改变数字本身的值,只用于增强可读性。
例如:
1_000_000
和:
1000000
数值完全一样。
3.3 使用 Java API 查看不同进制
除了手算之外,Java 也提供了相关工具方法。
public class RadixApiDemo {
public static void main(String[] args) {
int number = 97;
System.out.println(Integer.toBinaryString(number));
System.out.println(Integer.toOctalString(number));
System.out.println(Integer.toHexString(number));
}
}
得到:
1100001
141
61
还可以将指定进制字符串解析为整数:
int binary = Integer.parseInt("1100001", 2);
int hexadecimal = Integer.parseInt("61", 16);
System.out.println(binary);
System.out.println(hexadecimal);
得到:
97
97
这里的:
2
16
表示解析时使用的 radix(基数)。
3.4 字符为什么可以参与数字运算
计算机最终只能处理数字,因此字符也必须建立:
字符 ↔ 数字
之间的映射关系。
最经典的字符编码标准之一就是 ASCII:
American Standard Code for Information Interchange
常见字符对应关系:
| 字符 | 十进制值 |
| ----- | -------: |
| '0' | 48 |
| 'A' | 65 |
| 'a' | 97 |
例如:
public class CharacterNumberDemo {
public static void main(String[] args) {
char ch = 'A';
System.out.println(ch);
System.out.println((int) ch);
System.out.println(ch + 1);
}
}
结果:
A
65
66
其中:
(int) ch
可以直接观察 char 对应的数值。
而:
ch + 1
进入数值运算后,char 会参与数值类型提升,因此结果是:
66
而不是:
B
具体的类型提升规则将在下一章继续学习。
3.5 '0'、0 和 "0" 完全不同
这是初学者非常容易混淆的问题。
char a = '0';
int b = 0;
String c = "0";
三者分别是:
'0' → char
0 → int
"0" → String
其中:
'0'
对应的数值是:
48
所以:
System.out.println('0' + 1);
结果不是:
1
而是:
49
四、原理与进阶
4.1 有符号整数为什么会出现负数
对于 byte、short、int、long 等 Java 有符号整数,计算机采用二进制表示正数和负数。
Java 整数采用二进制补码(Two's Complement)语义。
例如 byte 一共有 8 位。
它能够表示:
-128 ~ 127
也就是:
-2⁷ ~ 2⁷ - 1
为什么不是:
0 ~ 255
因为 Java 的 byte 是有符号整数类型。
这一机制会直接影响后面学习的:
- 强制类型转换
- 整数溢出
- 位运算
- 原码、反码、补码
- 网络数据处理
下一章看到:
int value = 1500;
byte result = (byte) value;
结果变成负数时,真正的根源就在这里。
4.2 ASCII、Unicode 与 UTF-8 不完全是一回事
这三个概念经常被混在一起。
可以先建立最重要的层次关系。
ASCII
ASCII 主要解决早期英文字符、数字和符号的编码问题。
例如:
'A' → 65
'a' → 97
'0' → 48
Unicode
随着计算机走向全球化,仅有 ASCII 已经不够了。
需要表示:
中文
日文
韩文
阿拉伯文
Emoji
各种数学符号
……
于是需要一个统一的字符体系。
Unicode 的核心目标可以简单理解为:
给世界上的字符分配统一的编号。
这样的编号称为:
Unicode Code Point(Unicode 码点)
例如字符可能表示为:
U+0041
U+4E2D
UTF-8
UTF-8 则解决:
Unicode 字符编号真正转换成字节之后应该如何保存。
因此从概念上可以理解为:
Unicode
负责“字符编号是什么”
UTF-8
负责“这些编号怎样编码成字节”
UTF-8 是一种变长编码:
1~4 Byte
并且与 ASCII 的基本字符保持兼容。
字符集和字符编码将在 File 与 IO 阶段继续系统学习,本章只需要建立概念框架。
4.3 Java 的 char 并不一定等于一个完整 Unicode 字符
这是一个非常重要的 Java 细节。
Java:
char
使用 16 位保存一个 UTF-16 code unit。
对于大量常用字符,例如:
A
中
国
一个 char 就足够表示。
但是 Unicode 的范围已经超过了 16 位。
某些字符,例如部分 Emoji,需要两个 char 才能表示。
例如:
public class UnicodeDemo {
public static void main(String[] args) {
String text = "😀";
System.out.println(text);
System.out.println(text.length());
}
}
你可能直觉上认为:
😀
只有“一个字符”。
但 String.length() 得到的是 UTF-16 code unit 的数量,因此这里会得到:
2
所以以后处理 Unicode 文本时:
一个 char
不能简单等价于:
人眼看到的一个字符
这是 Java 字符串处理中的重要边界。
4.4 所有文件最终为什么都能保存为二进制
图片、音频、视频看起来与数字毫无关系,但磁盘最终保存的仍然是字节。
例如图片可以记录:
宽度
高度
像素
颜色
透明度
压缩信息
音频可以记录:
采样率
采样位数
声道
声音采样数据
视频又可以记录:
画面
声音
时间信息
压缩编码
这些信息最终都会按照文件格式约定转换成字节序列。
因此:
同一串 0 和 1
本身没有天然的“图片”或“音乐”含义。
真正决定含义的是:
数据格式 + 解析规则
可以把文件理解成:
按照某种协议组织起来的一串字节。
五、实践应用
进制和编码并不是只存在于考试题中。
在真实开发中,十六进制经常出现在:
颜色值
内存地址
二进制协议
文件格式
哈希值
调试信息
网络数据
权限标志
例如 Web 中常见:
#FF0000
表示红色。
其中:
FF = 255
而在调试底层数据时:
0xFF
0xCA
0xFE
往往比一长串二进制更加容易阅读。
随着后面学习:
- 位运算
- IO
- 网络编程
- JVM
- 字符编码
你会不断重新遇到本章知识。
所以进制不是孤立知识,而是理解计算机底层数据表示的入口。
六、常见问题
6.1 为什么 010 不是十进制 10?
因为 Java 中:
int number = 010;
前导 0 表示八进制。
所以:
010₈ = 8₁₀
实际开发中如果没有明确需要,通常不建议使用八进制字面量表示普通数字,以免产生阅读歧义。
6.2 08 为什么不能作为普通整数直接写?
八进制只能出现:
0~7
所以:
int number = 08;
是不合法的整数写法。
6.3 0x10 等于多少?
十六进制:
0x10
等于:
1 × 16¹ + 0 × 16⁰
因此数值是:
16
6.4 'A' 本身保存的是 65 吗?
在数值语义上:
char ch = 'A';
ch 对应的 UTF-16 code unit 数值是:
65
因此可以转换成:
int
进行数值处理。
6.5 ASCII 和 Unicode 谁替代谁?
不能简单理解成“Unicode 把 ASCII 删除了”。
Unicode 的基本拉丁字符区与 ASCII 建立了兼容关系。
例如:
A → 65
a → 97
0 → 48
这些经典编号仍然保持一致。
6.6 Unicode 和 UTF-8 是不是同一个概念?
不是。
简单记忆:
Unicode → 字符编号体系
UTF-8 → Unicode 的一种编码方式
以后学习 IO 时会进一步研究编码和解码。
6.7 一个 char 是否永远等于一个字符?
不是。
char 是:
16 位 UTF-16 code unit
某些 Unicode 字符需要两个 char 表示。
因此不要建立:
1 char = 世界上任意一个字符
这样的绝对认识。
七、练习与验收
7.1 知识问答
- 计算机为什么适合使用二进制表示和存储数据?
- bit 与 Byte 分别是什么?二者有什么关系?
- 为什么二进制每 3 位可以快速转换为一个八进制位?
- 为什么二进制每 4 位可以快速转换为一个十六进制位?
- Java 中二进制、八进制和十六进制整数分别使用什么前缀?
- ASCII 是什么?字符
'A'、'a'、'0'分别对应什么数值? '0'、0、"0"分别是什么类型?- Unicode 与 UTF-8 分别解决什么问题?
- 为什么图片、音频、视频最终都可以保存成二进制数据?
- 为什么不能简单认为一个 Java
char永远代表一个完整 Unicode 字符? - 请将
0b01100001、0141、0x61转换成十进制,并判断三者之间有什么关系。 - 为什么 Java 代码中的
010和10数值不同?
7.2 代码阅读
禁止运行下面的代码,先预测结果:
public class BinaryRead01 {
public static void main(String[] args) {
int a = 0B01100001;
int b = 0141;
int c = 0X61;
char ch = 'A';
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(ch + 1);
}
}
完成:
- 写出四行输出结果。
- 解释为什么三种不同进制写法可以表示相同数值。
- 解释为什么
ch + 1的结果不是直接显示字符B。
继续分析:
public class RadixRead02 {
public static void main(String[] args) {
int a = 0b1010;
int b = 012;
int c = 0xA;
System.out.println(a == b);
System.out.println(b == c);
}
}
要求:
- 不运行代码预测结果。
- 分别写出三个整数的十进制值。
- 解释变量保存的是“进制形式”还是最终数值。
7.3 手写代码
- 不查询资料,手算十进制
6、13、37、255的二进制形式。 - 手写程序分别定义二进制、八进制、十进制、十六进制整数,使四个变量保存相同数值。
- 使用
Integer.toBinaryString()、Integer.toOctalString()和Integer.toHexString()输出整数255的三种表示形式。 - 编写程序输出
'A'、'a'、'0'对应的整数值。 - 编写程序验证一个普通中文字符和一个 Emoji 在
String.length()上的差异。
7.4 Debug
下面代码存在问题:
public class RadixDebug01 {
public static void main(String[] args) {
int number = 08;
System.out.println(number);
}
}
要求:
- 判断问题发生在编译期还是运行期。
- 找出错误涉及哪一种进制规则。
- 分别修改成合法的十进制
8和八进制8的表示方式。
继续分析:
public class CharacterDebug02 {
public static void main(String[] args) {
char value = 'AB';
System.out.println(value);
}
}
要求:
- 判断代码是否合法。
- 说明
char字面量的基本限制。 - 如果需要表示
"AB",应该考虑使用什么类型?
7.5 综合训练
设计一个“数字表示观察器”。
暂时不使用键盘输入,固定:
int number = 97;
程序需要输出:
十进制形式
二进制形式
八进制形式
十六进制形式
如果将该数字转换成 char,对应什么字符
要求:
- 合理拆分代码。
- 输出格式清晰。
- 不允许直接把所有答案写死在字符串中。
- 完成后说明十进制
97与字符'a'之间的关系。
7.6 本章验收
在不查看资料的情况下完成:
- [ ] 能解释二进制为什么成为计算机底层数据表示基础。
- [ ] 能说出 bit、Byte 的关系。
- [ ] 能手算基础的二进制与十进制转换。
- [ ] 能解释八进制与十六进制为什么方便表示二进制。
- [ ] 能正确书写 Java 的二进制、八进制和十六进制整数。
- [ ] 能解释 ASCII 中
'A'、'a'、'0'的经典编号。 - [ ] 能区分 ASCII、Unicode 和 UTF-8 的基本角色。
- [ ] 能解释为什么
char + 1会产生整数结果。 - [ ] 能识别 Java 八进制前导
0带来的常见问题。 - [ ] 能解释为什么一个
char不一定等价于一个完整 Unicode 字符。
如果以上内容仍有多项需要查资料才能完成,则本章暂时不应视为真正掌握。