爱上JDK源码阅读-Integer

知识点
  • 自动装箱和拆箱
  • IntegerCache机制
  • toString()实现算法优化
从一道面试题开始
Integer a = 100;
int b = 100;
if (a == b) {
    System.out.println("a == b");
} else {
    System.out.println("a != b");
}

聪明的你应该马上可以知道答案了,输出就是 a == b

自动装箱和自动拆箱

那你可以说说自动装箱和拆箱是怎么回事吗?

在这里插入图片描述

把代码编程成字节码,很容易就可以看出来。在这里推荐Compiler Explorer这个小工具,在浏览器直接搜索就可以了。

当代码中声明Integer a = 100; 实际上在编译成的时候会自动转换成 Integer a = Integer.valueOf(100); 这个过程就是自动装箱

当Integer类型和基础类型int在比较的时候,Integer类型会自动转换为int类型。a == b 实际上编译成 a.intValue() == b, 这个过程就是自动拆箱。 这个过程就是自动拆箱

在上面的截图中,可以看过这个过程。

Integer.valueof() 方法
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

在自动装箱过程中,会调用Integer.valueOf()方法,首先会检查需要变量i在不在IntegerCache范围内,如果有直接返回,没有则new一个出来。

Integer.intValue()
public int intValue() { return value; }

该方法非常简单,直接返回value。

IntegerCache
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

上述代码的实现逻辑比较简单:

1、比较IntegerCache的取值范围,low是-128,high是127,其中high可以通过修改系统变量去设置。

2、创建一个cache数组,依次遍历创建Integer对象放到数组之中。

接着继续看看下面的程序会输出什么结果

Integer a = 128;
int b = 128;
if (a == b) {
    System.out.println("a == b");
} else {
    System.out.println("a != b");
}

Integer c = 128;
if (a == c) {
    System.out.println("a == c");
}else {
    System.out.println("a != c");
}

输出结果

a == b
a != c

解释一下:

a和b在比较的时候,a是Integer类型,b是int类型,a在编译的时候会自动拆箱,实际是 a.intValue() == b, 两个int之间的比较,实际上比较是数值,所以是相等的。

a和c在时间的时候,a和c都是Integer类型,不会发生自动拆箱,而是两个对象之间的比较,所以是不相等的。

趁热打铁,看看下面这两组

Integer d = 100;
Integer e = 100;
if (d == e) {
    System.out.println("d == e");
}else {
    System.out.println("d != e");
}

Integer m = new Integer(100);
Integer n = new Integer(100);
if (m == n) {
    System.out.println("m == n");
}else {
    System.out.println("m != n");
}

输出结果

d == e
m != n

解释

为什么d和e两个对象之间比较,输出结果是相等呢,这个就要说到IntegerCache机制了,在声明d和e的时候,编译时会自动装箱,也就是实际上时 Integer e = Integer.valueOf(100). 在默认情况下,IntegerCache的范围在-128-127. 所以d和e时相同的。而m和n强制使用new Integer去初始化变量,m和n返回的是不同的对象,所以是不相等的。

小结

1、自动装箱是发生在声明Integer变量时,且初始化表达式是个常量,会调用Integer.valueOf()方法,创建对象有IntegerCache机制。

2、自动拆箱时发生在Integer类型和int类型比较的时候, 会自动调用Integer.intValueOf()方法。

3、IntegerCache缓存机制默认范围是-128-127。

Integer.toString()实现
public static String toString(int i, int radix) {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10;

    /* Use the faster version */
    if (radix == 10) {
        return toString(i);
    }

    char buf[] = new char[33];
    boolean negative = (i < 0);
    int charPos = 32;

    if (!negative) {
        i = -i;
    }

    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)];
        i = i / radix;
    }
    buf[charPos] = digits[-i];

    if (negative) {
        buf[--charPos] = '-';
    }

    return new String(buf, charPos, (33 - charPos));
}

这段代码定义了一个方法,用于将长整型(long)数字转换为指定基数(radix)的字符串表示

1、基数检查,只有是2-36进制

2、10进制特殊处理

3、声明一个char数据,为什么是33呢,int类型是32位,加上’-',所以一共是33

4、处理负数

5、while循环,计算余数和商,直到最后一个商小于基数的时候退出循环

6、处理最后一个商

7、处理负数

8、将char数组转换位String对象

public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}
static void getChars(int i, int index, char[] buf) {
    int q, r;
    int charPos = index;
    char sign = 0;

    if (i < 0) {
        sign = '-';
        i = -i;
    }

    // Generate two digits per iteration
    while (i >= 65536) {
        q = i / 100;
    // really: r = i - (q * 100);
        r = i - ((q << 6) + (q << 5) + (q << 2));
        i = q;
        buf [--charPos] = DigitOnes[r];
        buf [--charPos] = DigitTens[r];
    }

    // Fall thru to fast mode for smaller numbers
    // assert(i <= 65536, i);
    for (;;) {
        q = (i * 52429) >>> (16+3);
        r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
        buf [--charPos] = digits [r];
        i = q;
        if (i == 0) break;
    }
    if (sign != 0) {
        buf [--charPos] = sign;
    }
}

在上述代码中,比较引人注意的是有两个小片段

// Generate two digits per iteration
while (i >= 65536) {
    q = i / 100;
// really: r = i - (q * 100);
    r = i - ((q << 6) + (q << 5) + (q << 2));
    i = q;
    buf [--charPos] = DigitOnes[r];
    buf [--charPos] = DigitTens[r];
}

这里使用了两个技巧。

第一个不再是用除以10去计算商,而是使用除以100,一次循环中计算两次

第二个是计算余数,不是使用%运算,也不是 r = i - (q * 100); 而是使用 r = i - ((q << 6) + (q << 5) + (q << 2));

这里没有使用乘法,而是使用左移和加法代替。

让我们逐步解释这个表达式:

q << 6:这是将 q 左移 6 位,相当于 q 乘以 (2^6 = 64)。

q << 5:这是将 q 左移 5 位,相当于 q 乘以 (2^5 = 32)。

q << 2:这是将 q 左移 2 位,相当于 q 乘以 (2^2 = 4)。

这三个位移操作分别模拟了 q 乘以 64、32 和 4。加在一起,它们就模拟了 q 乘以 100(因为 (64 + 32 + 4 = 100))。

再来看另外一个片段

for (;;) {
        q = (i * 52429) >>> (16+3);
        r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
        buf [--charPos] = digits [r];
        i = q;
        if (i == 0) break;
    }

首先,在计算商的时候并不是一个常规的除法运算。看到 q = (i * 52429) >>> (16+3); 这里并没有使用除法,代替的是 乘法 和 右移运算。

这行代码计算了i除以10的商。这里使用了52429这个特殊的数。这个数是通过100000 / 10(即10000除以10,再左移5位)得到的。这样,i * 52429相当于i乘以10000再左移5位。然后,通过无符号右移操作>>>来除以(2^19) (即右移19位),相当于再除以(2^14)(即65536)。这样,q就得到了i除以10的商的近似值。

由上面两个小片段中可以知道,在库函数级别的代码中,对代码和性能的要求非常高。

总结

1、自动装箱和拆箱 - 是一种语法糖,在编译器就实现。

2、IntegerCache机制 - 一种缓存机制,节约内存,避免对象重复创建。

3、toString()实现算法优化 - 使用位移算法替换乘法运算,使用乘法运算替换除法运算。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/559762.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SQL】DISTINCT GROUP BY

找到所有办公室里的所有角色&#xff08;包含没有雇员的&#xff09;,并做唯一输出(DISTINCT) 用DISTINCT : SELECT DISTINCT B.Building_name,E.Role FROM Buildings B LEFT JOIN Employees EON B.Building_name E.Building需要找到的结果&#xff1a;所有办公室名字&#…

C# 图像旋转一定角度后,对应坐标怎么计算?

原理分析 要计算图像内坐标在旋转一定角度后的新坐标&#xff0c;可以使用二维空间中的点旋转公式。假设图像的中心点&#xff08;即旋转中心&#xff09;为 (Cx, Cy)&#xff0c;通常对于正方形图像而言&#xff0c;中心点坐标为 (Width / 2, Height / 2)。给定原坐标点 (X, …

10:HAL---高级定时器

前言&#xff1a; 高级定时器具有通用定时器的所有功能&#xff0c;我们在这里面只说它不一样的地方。&#xff08;通用定时器不具备的功能&#xff09; 一&#xff1a;高级定时器 1&#xff1a;介绍 2&#xff1a;重复计数器 在我们普通的定时器中当CNTCCR时直接发生溢出。然…

阿里二面凉了,难蹦。。。

分享一位同学阿里巴巴的后端面经&#xff0c;共有 2 面&#xff0c;第一面很顺利过了&#xff0c;可惜挂在第二面。 这两面的知识点范围&#xff0c;我帮大家罗列一下&#xff1a; 网络&#xff1a;TCP、HTTP mysql&#xff1a;索引应用、索引结构、隔离级别、最左匹配 redis…

图小灵的多线程

进程 简而言之,一个运行的程序就叫作进程 管理进程,要先将进程使用类/结构体,表示各个属性 为了后续增删改查,需要将进程通过数据结构串联起来 系统中有一个结构体专门用来表示进程的属性,叫作PCB(进程控制块) 一个进程使用一个或者多个PCB来表示 系统会使用类似于双向链…

pycharm-git 配置(1)

1.安装git2.pycharm 中配置git 插件 弹出Git版本号&#xff0c;即配置成功。3.创建本地仓库 VCS->VCS operations->create repository->设置本地目录 左下角可以看到git本地仓库git可以看到push,commit。 4.配置远方仓库&#xff0c;此时确保git上是有这个项目…

在开发板上运行spidev_test报错:“./spidev_test: line 2: h: not found”

问题 今天交叉编译spidev_test后&#xff0c;放到开发板上运行报错&#xff1a;“./spidev_test: line 2: h: not found” 原因 编译方式不同&#xff0c;生成的是64为程序&#xff0c;应该生成32位的程序。 解决办法&#xff1a; 修改为直接用命令编译&#xff0c;生成…

酷开科技抓住“客厅经济”发展的机遇,不断对酷开系统升级赋能

酷开科技抓住“客厅经济”发展的机遇&#xff0c;不断对酷开系统升级赋能&#xff0c;打造新的生活场景&#xff0c;满足消费者的不同生活需求&#xff0c;酷开科技的产品和服务让消费者能够在家庭空间中享受到更加智能、便捷和温馨的时光。同样凭借更加包容、开放的生态体验&a…

贪吃蛇项目实战——学习详解

前言:贪吃蛇是一个经典的游戏&#xff0c; 本节将使用c语言实现一个简易的的贪吃蛇小游戏。 本节内容适合已经学完c语言还有数据结构链表的友友们。 我们要实现的贪吃蛇是在控制台进行游戏的。 它运行起来是这样的&#xff1a; 贪吃蛇 那么&#xff0c; 为了实现这个小游戏。 我…

添加Redis缓存

1.缓存查询 在service层Impl文件中&#xff0c;进行查询时优先向Redis中查数据&#xff0c;查到就查到了&#xff0c;没有查到向mysql数据库中查&#xff0c;查到之后不先返回&#xff0c;而是先将数据存到数据库&#xff08;缓存&#xff09;,在再返回数据。 1.1 代码实现(缓…

鸿蒙端云一体化开发--调用云函数--适合小白体制

如何实现在端侧调用云函数&#xff1f; 观看前&#xff0c;友情提示&#xff1a; 不知道《如何一键创建端云一体化模板》的小白同学&#xff0c;请看&#xff1a; 鸿蒙端云一体化开发--开发云函数--适合小白体制-CSDN博客 实现方法&#xff1a; 第一步&#xff1a;添加依赖 …

98%的企业与被入侵的第三方有关联,如何有效的防止入侵

技术供应链漏洞使威胁参与者能够以最小的努力扩展其运营&#xff0c;在导致第三方入侵的外部B2B关系中&#xff0c;75%涉及软件或其他技术产品和服务&#xff0c;其余25%的第三方违规涉及非技术产品或服务。 入侵通常需要几个月或更长的时间才能公之于众&#xff0c;受害者可能…

【Leetcode】string类刷题

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;Leetcode刷题 目录 1.仅反转字母2.字符串中第一个唯一字符3.验证回文串4.字符串相加5.反转字符串I I6.反转字符串中的单词III7.字符串相乘8.把字符串转换为整数 1.仅反转字母 题目链接&#xff1a;…

C++:模板详解

模板详解 1.函数模板1.概念2.语法3.原理4.实例化1.隐式实例化2.显示实例化 5.匹配原则 2.类模板1.格式2.实例化 3.非类型模板参数注意点 4.特化1.概念2.函数模板特化1.前提2.语法说明3.示例 3.类模板特化1.全特化2.偏特化/半特化3.选择顺序 4.按需实例化 5.模板的分离编译1.分离…

开发一个农场小游戏需要多少钱

开发一个农场小游戏的费用因多个因素而异&#xff0c;包括但不限于游戏的规模、复杂性、功能需求、设计复杂度、开发团队的规模和经验&#xff0c;以及项目的时间周期等。因此&#xff0c;无法给出确切的费用数字。 具体来说&#xff0c;游戏的复杂程度和包含的功能特性数量会直…

巧用断点设置查找bug【debug】

默认设置的断点&#xff0c;当代码运行到断点处MCU就会被挂起&#xff0c;从而停在断点处。 但在某些情况下&#xff0c;如调试FCCU时&#xff0c;如果设置断点&#xff0c;MCU停下后将会导致 FCCU 配置WDG超时。或在调试类似电机控制类的应用时&#xff0c;不适当的断点会导 致…

镜舟科技荣获金科创新社 2024 年度金融数据智能解决方案奖

近日&#xff0c; 镜舟科技凭借领先的金融实时数仓构建智能经营解决方案&#xff0c;在“金科创新社第六届金融数据智能优秀解决方案评选”活动中&#xff0c;成功入选“数据治理与数据平台创新优秀解决方案”榜单。 金科创新社主办的“鑫智奖”评选活动&#xff0c;旨在展示…

详解IIC通信协议以及FPGA实现

一、IIC简介 IIC也称为I2C&#xff08;Inter-Integrated Circuit&#xff09;由飞利浦公司&#xff08;现在的恩智浦半导体&#xff09;开发&#xff0c;是一种用于短距离数字通信的串行&#xff0c;同步&#xff0c;半双工通信接口协议&#xff1b;传输在标准模式下可以达到10…

python:元组,字符串,切片

一、元组# 列表可以修改内容&#xff0c;元组可以不被修改 # 在程序内封装数据&#xff0c;不希望数据被篡改&#xff0c;所以使用元组 # 语法&#xff1a; 不限制类型 # 定于元组的字面量&#xff1a; &#xff08;元素&#xff0c;元素&#xff0c;元素.....&#xff09; # 定…

apipost、postman等工具上传图片测试flask、fastapi的文件api接口

参考&#xff1a;https://blog.csdn.net/qq_15821487/article/details/119354129 https://www.cnblogs.com/wyxjava/p/16076176.html 选择from-data&#xff0c;下拉选择file上传文件发送即可