作者介绍:大家好,我是 CodeStats。一个在底层技术上"考古"了四年的硬核爱好者,也是 WWAIC(全周项目AI编程)范式的提出者和实践者。我曾手写过一个完整的 Java Web 框架(从 IoC 容器到嵌入式 Tomcat,代码全开源),也喜欢用通俗的语言拆解 CPU、JVM、操作系统的运行本质。我的技术信条:所有高深的技术,最后都能用大白话讲清楚。如果讲不清楚,说明还没真正理解。
本文能获得什么
-
掌握 Java 八种基本数据类型的底层存储原理 :理解补码、IEEE 754、JVM 存储模型,搞懂为什么
0.1 + 0.2 != 0.3 -
搞懂数字、字符、字节数组的互相转换本质 :从
parseInt的逐字符累加到getBytes的编码查表,彻底理解转换底层 -
理解封装类、BigDecimal 的设计原理 :知道封装类缓存机制、BigDecimal 的
unscaledValue × 10^-scale设计
目录
-
提问一:Java 类都是基于底层基础数据类型封装,Java 八种基本数据类型存储原理
-
提问二:Java 最核心三类数据类型------数字、字符、字节数组是如何互相转换的,代码示例演示原理
-
提问三:Java 的封装类型底层原理和日期、BigDecimal 的实现原理
提问一:Java 类都是基于底层基础数据类型封装,Java 八种基本数据类型存储原理
1.1 计算机底层的原子:比特
计算机里最终只有比特------0 和 1。所有数据类型,本质上都是对固定长度比特序列的约定解释。
text
8 个比特 → 一个字节
32 个比特 → 一个 int
64 个比特 → 一个 long 或 double
Java 的八种基本类型,就是对固定长度比特序列的不同解释方式。
1.2 整数类型:补码
Java 的 byte、short、int、long 全部使用二进制补码存储。补码的设计解决了正负运算统一和 +0/-0 两个零的问题。
补码规则:
-
正数的补码就是其本身
-
负数的补码 = 反码 + 1
-
最高位是符号位,0 表示正数,1 表示负数
| 类型 | 位数 | 范围 |
|---|---|---|
byte |
8 | -128 ~ 127 |
short |
16 | -32768 ~ 32767 |
int |
32 | -2³¹ ~ 2³¹-1 |
long |
64 | -2⁶³ ~ 2⁶³-1 |
示例:int 类型的 -1
text
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111 (即 0xFFFFFFFF)
所以 int 类型的 0xFFFFFFFF 就是 -1。
溢出本质 :补码的位数固定,超出范围就截断。Integer.MAX_VALUE + 1 = Integer.MIN_VALUE,因为 32 位补码的 0x7FFFFFFF + 1 = 0x80000000,最高位变成 1,解释为负数。
1.3 浮点类型:IEEE 754
float 和 double 使用 IEEE 754 标准,把 32/64 位拆成三部分:
text
float = 1 位符号 + 8 位指数 + 23 位尾数
double = 1 位符号 + 11 位指数 + 52 位尾数
值的计算公式:
text
(-1)^符号 × 1.尾数 × 2^(指数 - 偏移)
为什么 0.1 + 0.2 != 0.3?
因为十进制小数 0.1 在二进制中是无限循环小数,无法用有限位精确表示。double 只能存储它的近似值。两个近似值相加,结果也是近似值,所以显示为 0.30000000000000004。
1.4 char:16 位无符号整数
char 是 16 位无符号整数,表示一个 UTF-16 代码单元。'A' 就是 65,'中' 就是 0x4E2D(20013)。
java
char c = '中';
System.out.println((int) c); // 20013
1.5 boolean:JVM 内部用 int 或 byte
JLS 没有规定 boolean 的大小。JVM 规范中,boolean 在局部变量和操作数栈里通常按 int 处理,在数组中可能按 byte 处理。
1.6 基本类型的 JVM 存储位置
-
局部变量:存放在 JVM 虚拟机栈的局部变量表中
-
成员变量(未被 static 修饰) :存放在 Java 堆中
-
静态变量:存放在方法区(JDK 8 后在元空间)
提问二:Java 最核心三类数据类型------数字、字符、字节数组是如何互相转换的
2.1 字符与数字:本质是编码表映射
字符 '0' ~ '9' 的 ASCII 码是 48 ~ 57。所以:
java
int digit = '9' - '0'; // 9
char c = (char) ('0' + 9); // '9'
2.2 字符串 → 整数:逐字符乘 10 加位
Integer.parseInt("123") 的底层逻辑:
java
int result = 0;
for (int i = 0; i < s.length(); i++) {
int digit = s.charAt(i) - '0';
result = result * 10 + digit;
}
同时做溢出检查------一旦发现累加会溢出,立即抛出 NumberFormatException,不需要读完整个字符串。
2.3 整数 → 字符串:除 10 取余
Integer.toString(123) 的底层逻辑:
java
// 简化版
char[] buf = new char[10];
int i = buf.length;
while (n > 0) {
buf[--i] = (char) ('0' + n % 10);
n /= 10;
}
JDK 源码中使用了 DecimalDigits 工具类,通过查表加速转换。
2.4 字符串 → 字节数组:按 Charset 编码
这不是强转,而是编码查表:
java
byte[] utf8 = "中".getBytes(StandardCharsets.UTF_8);
// [-28, -72, -83],即 E4 B8 AD
byte[] gbk = "中".getBytes("GBK");
// [-42, -48],即 D6 D0
同一个"中",UTF-8 是 3 字节,GBK 是 2 字节。
2.5 字节数组 → 字符串:按 Charset 解码
java
String s = new String(new byte[]{(byte)0xE4, (byte)0xB8, (byte)0xAD},
StandardCharsets.UTF_8);
// "中"
2.6 数字 ↔ 字节数组:移位 + 掩码
int → byte4:
java
int x = 0x12345678;
byte[] bs = new byte[4];
bs[0] = (byte)(x >>> 24); // 0x12
bs[1] = (byte)((x >>> 16) & 0xFF); // 0x34
bs[2] = (byte)((x >>> 8) & 0xFF); // 0x56
bs[3] = (byte)(x & 0xFF); // 0x78
byte4 → int:
java
int y = ((bs[0] & 0xFF) << 24)
| ((bs[1] & 0xFF) << 16)
| ((bs[2] & 0xFF) << 8)
| (bs[3] & 0xFF);
& 0xFF 的作用:byte 是有符号的,0xE4 作为 byte 是 -28,转成 int 时会符号扩展成 0xFFFFFFE4。& 0xFF 只保留低 8 位,变成干净的 228。
2.7 转换规律总表
| 转换 | 底层做法 |
|---|---|
char → int |
零扩展,char 无符号 |
int → char |
取低 16 位 |
'9' → 9 |
c - '0' |
9 → '9' |
(char)('0' + 9) |
String → int |
result = result * 10 + digit,溢出检查 |
int → String |
% 10、/ 10,反转 |
String → byte[] |
Charset 编码查表 |
byte[] → String |
Charset 解码查表 |
int → byte[] |
>>> 移位 + & 0xFF 掩码 |
byte[] → int |
& 0xFF + << + ` |
提问三:Java 的封装类型底层原理和日期、BigDecimal 的实现原理
3.1 封装类:内部就是一个基本类型字段
| 封装类 | 内部字段 |
|---|---|
Byte |
final byte value |
Short |
final short value |
Integer |
final int value |
Long |
final long value |
Float |
final float value |
Double |
final double value |
Character |
final char value |
Boolean |
final boolean value |
Integer 类通过 valueOf() 方法实现自动装箱,自动拆箱则通过继承自 Number 类的 intValue() 方法实现。
3.2 缓存机制
Integer、Long、Short、Byte 默认缓存 -128 到 127 之间的值,Character 缓存 0 到 127,Boolean 直接返回 TRUE/FALSE。
java
Integer a = 100, b = 100;
System.out.println(a == b); // true,同一缓存对象
Integer c = 1000, d = 1000;
System.out.println(c == d); // false,不同对象
3.3 BigDecimal:BigInteger + scale
BigDecimal 的核心设计:
text
BigDecimal = unscaledValue × 10^(-scale)
内部由 BigInteger intVal 和 int scale 组成。
| 值 | unscaledValue | scale |
|---|---|---|
123 |
123 | 0 |
-1.5 |
-15 | 1 |
1e10 |
1 | -10 |
运算规律:
-
加法:对齐 scale,再 BigInteger 加
-
乘法:unscaledValue 相乘,scale 相加
-
除法:可能除不尽,必须指定舍入模式
-
compareTo只比数值,equals还比 scale
3.4 日期类的底层:long 时间戳
java.util.Date 内部只有一个 long fastTime,表示自 1970-01-01T00:00:00 UTC 以来的毫秒数。java.sql.Timestamp 额外有一个 int nanos 表示纳秒部分。
总结
上篇从最底层的比特出发,梳理了 Java 八种基本类型的存储原理(补码、IEEE 754、UTF-16),然后深入数字、字符、字节数组的互相转换本质(乘 10 加位、除 10 取余、移位掩码、编码查表),最后剖析了封装类的字段结构、缓存机制以及 BigDecimal 的 unscaledValue × 10^-scale 设计。这些是 Java 数据类型体系的基石。
👍 点赞 · ⭐ 收藏 · 🔔 关注
如果本文对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续输出底层干货的最大动力!