JDK21 Integer 源码剖析:聊聊大神们在整数上玩的那些“骚操作”

嘿,Friends!

我们每天写代码,intInteger 估计是用得最多的类型之一了,对吧?Integer i = 100;,简单得就像呼吸一样。但你有没有想过,这句简单的代码背后,JDK 的大神们都做了些什么?

一:IntegerCache —— 你以为的 new,其实是“二手”的

我们先来看一个经典的面试题:

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false

为啥会这样?答案就在 Integer 的一个静态内部类:IntegerCache

简单来说,IntegerCache 就是个缓存。JDK 觉得,大家用的小整数(-128 到 127)实在是太多了,每次都 new 一个新对象太浪费内存和性能了。干脆,我启动的时候就一次性把这 256 个整数对象全创建好,放一个数组里。以后你要用,直接从数组里拿,别 new 了!

这就是典型的 “空间换时间” 思想。用一点点内存(一个 256 大小的数组)来换取极高的访问速度和更少的垃圾回收(GC)压力。

1.1.源码探秘:

// Integer.java
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;
    static final Integer[] archivedCache; // 用于 GraalVM 的一个优化

    static {
        // high 的值可以由外部属性配置,但默认是 127
        int h = 127;
        String s = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (s != null) {
            // ... 解析属性,设置 h ...
        }
        high = h;

        // ... 省略 GraalVM 相关逻辑 ...

        // 创建缓存数组
        cache = new Integer[high - low + 1];
        int j = low;
        // 循环创建对象,填满缓存
        for(int k = 0; k < cache.length; k++) {
            cache[k] = new Integer(j++);
        }
    }
}

当我们写 Integer i = 100; 时,实际上编译器会把它转换成 Integer.valueOf(100);。我们来看看 valueOf 方法:

// Integer.java
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)]; // 直接从缓存拿
    return new Integer(i); // 超出范围,才 new 一个新的
}

真相大白!当值在 [-128, 127] 范围内,valueOf 直接从 IntegerCache.cache 数组里返回一个已经创建好的对象。所以 ab 指向的是同一个对象,a == btrue。而 200 超出了这个范围,每次都会 new 一个新对象,所以 cd 指向不同对象,c == dfalse

1.2.图解:

这个小小的缓存,完美地体现了设计的智慧:优化最常见的场景

 

二:toString() 背后的功臣 —— stringSizegetChars

把一个 int 转成 String,比如 Integer.toString(12345),这背后也有大学问。

一个直观但低效的做法是:

  • 创建一个可变 StringBuilder

  • 用循环和 % 10/ 10 操作,一位一位地把数字算出来,appendStringBuilder

  • 最后 reverse() 一下,再 toString()

这个过程涉及到多次方法调用、可能的扩容和反转,效率不高。JDK 的大神们当然不屑于这么做。他们用了两个“秘密武器”:stringSizegetChars

2.1. stringSize(int x): 内联优化, 循环展开

你怎么算一个 int 的十进制长度?log10?太慢了!循环除以10?还是慢!看看大神们的写法:

    // 开启内联优化, jdk 16 开启有的
    @IntrinsicCandidate
    public static String toString(int i) {
        int size = stringSize(i);
        if (COMPACT_STRINGS) {
            byte[] buf = new byte[size];
            getChars(i, size, buf);
            return new String(buf, LATIN1);
        } else {
            byte[] buf = new byte[size * 2];
            StringUTF16.getChars(i, size, buf);
            return new String(buf, UTF16);
        }
    }

    

static int stringSize(int x) {
        
        // d 表示符号位长度:负数时 d = 1(需要负号),非负数时 d = 0
        int d = 1;
        if (x >= 0) {
            d = 0;
         // 将整数变为负数统一处理, 防止溢出.
         // 若 x >= 0,将 x 转为负数(统一用负数处理,避免边界问题)
            x = -x;  // 非负数转为负数(如 42 → -42)
        }
        int p = -10;
        for (int i = 1; i < 10; i++) {
            if (x > p)
                return i + d;
            p = 10 * p;
        }
        return 10 + d;
    }

核心功能

  • 输入:整数 x(任意 int 值)

  • 输出x 的字符串表示的长度(如 -42 的长度为 3,123 的长度为 3)

关键逻辑

  1. 处理符号位

  • d 表示符号位长度:负数时 d = 1(需要负号),非负数时 d = 0
  • 若 x >= 0,将 x 转为负数(统一用负数处理,避免边界问题):
if (x >= 0) {
    d = 0;
    x = -x; // 非负数转为负数(如 42 → -42)
}

2.2.循环检测数字位数

  • 初始化 p = -10(代表 -10^1,即数字位数为 1 的阈值)。

  • 循环 i 从 1 到 9(i 表示当前检测的数字位数):

  • for (int i = 1; i < 10; i++) {
        if (x > p) // 判断 x 的绝对值是否 < 10^i
            return i + d; // 满足则返回总长度 = 数字位数(i) + 符号位(d)
        p = 10 * p; // 更新 p 为 -10^(i+1)(如 -10 → -100)
    }

    终止条件:当 x > p 时,说明 x 的绝对值小于 10^i,数字位数为 i

  • 例:x = -5(绝对值 5),首次循环 p = -10-5 > -10 → 返回 1 + d

2.3.处理最大位数(10 位)

  • 若循环结束未返回,说明 x 是 10 位数(如 Integer.MIN_VALUE 或 Integer.MAX_VALUE):

return 10 + d; // 10 位数字 + 符号位

 

边界示例:

x 值字符串长度计算过程
0"0"1x>=0 → d=0x=0 → 循环: 0 > -10 → 1+0=1
42"42"2x>=0 → d=0x=-42 → 循环: -42 > -100 → 2+0=2
-10"-10"3d=1, 循环: -10 > -10 不成立 → 下一轮 -10 > -100 → 2+1=3
2147483647 (MAX_VALUE)"2147483647"10x>=0 → d=0x=-2147483647 → 循环 9 次后未返回 → 10+0=10
-2147483648 (MIN_VALUE)"-2147483648"11d=1, 循环 9 次未返回 → 10+1=11

2.4.设计特点

  1. 负数统一处理:将非负数转为负数,避免正数边界问题(如 Integer.MAX_VALUE)。

  2. 线性搜索优化:基于数据偏向小数值的特点,优先检查较小位数,平均效率高。

  3. 循环展开友好:固定循环 9 次,编译器易优化(如循环展开内联)。

  4. 固定循环次数(9 次)的意义

    在代码中,循环边界是硬编码的:for (int i = 1; i < 10; i++)

    由于 Java int 的范围是 -2^31 到 2^31-1(即 -2147483648 到 2147483647),其字符串表示的最大长度是:

    负数:11 位(如 -2147483648

    正数:10 位(如 2147483647)

    9 次循环的合理性:

    数字 1~9 位:通过循环内返回(如 123 在第 3 次循环返回)

    数字 10 位:循环结束后返回(如 2147483647

    负数符号位:通过变量 d 单独处理

    编译器优化:循环展开(Loop Unrolling):

    编译器会将固定次数的循环转换为顺序执行的代码块,消除循环控制开销

    ```java

    for (int i = 1; i < 10; i++) {
        if (x > p) return i + d;
        p = 10 * p;
    }

    ```

    循环展开后的等效代码(编译器自动生成)

    // i=1
    if (x > -10) return 1 + d;  // 检查 1 位数
    p = -100;

    // i=2
    if (x > -100) return 2 + d; // 检查 2 位数
    p = -1000;

    // i=3
    if (x > -1000) return 3 + d;
    p = -10000;

    // ... 重复直到 i=9

    // i=9
    if (x > -1000000000) return 9 + d;

    // 默认返回 10 位数
    return 10 + d;

    为何循环展开更高效
    优化类型原始循环循环展开后优势
    指令跳转每次迭代需跳转回循环起始无跳转(顺序执行)消除分支预测错误惩罚
    循环变量维护需检查 i<10 和 i++无需维护循环计数器减少 CPU 指令
    CPU 流水线循环控制中断流水线连续顺序指令提高指令级并行
    缓存局部性代码分散连续代码块更好利用 CPU 缓存
     内联(Inlining)优化

    当此方法被频繁调用时(如 Integer.toString() 中):

    内联机制:编译器将方法体直接复制到调用处

    与循环展开协同

    // 调用点示例
    void print(int num) {
        int size = stringSize(num); // 方法调用
        // ... 使用 size
    }
    
    // 内联+展开后:
    void print(int num) {
        // 直接展开的 stringSize 逻辑
        int d = 1;
        if (num >= 0) { ... }
        if (num > -10) { ... } // 展开代码块 1
        if (num > -100) { ... } // 展开代码块 2
        // ...
    }

  • MIN_VALUE 处理:直接使用原值(不取负),避免溢出。

  • 符号位独立:负数长度 = 数字位数 + 1,非负数长度 = 数字位数。

2.5.固定9次循环:

固定 9 次循环的设计本质是用空间换时间

1.空间:通过循环展开轻微增加代码体积(约 9 个条件块)

2.时间:获得显著性能提升:

  • 消除循环控制开销

  • 优化分支预测

  • 实现高效内联

  • 利用 CPU 流水线和缓存局部性

结语:于细微处见真章

我们今天只是探索了 Integer 源码的冰山一角,但已经能感受到其中蕴含的巨大智慧:

  • IntegerCache 教会我们:缓存高频数据,用空间换时间,这是提升性能最直接有效的手段之一。

  • stringSize 告诉我们:条条大路通罗马,但有些路就是快得多。要敢于用看似“笨拙”但极度高效的 if-else 替代复杂的数学运算。

代码是冰冷的,但优秀的代码背后,是工程师对性能的极致追求和对计算机体系的深刻理解。

下次当你再写下 Integer i = 123; 时,希望你能会心一笑,想起它背后那些默默无闻但功勋卓著的“骚操作”。

有空多逛逛 JDK 源码,你会发现一个全新的、充满惊喜的世界!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nextera-void

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值