Java 自动装箱、拆箱引起的耗时
耗时问题
在说 Java 的自动装箱和自动拆箱之前,我们先看一个例子。
这个错误我在项目中犯过(尴尬),拿出来共勉!
1 | private static long getCounterResult() { |
在我的电脑(macOS 64位系统,配置较高),打印结果如下:
1 | result = 2305843005992468481, and take up time : 12s |
居然使用了 12s
,是可忍叔
不可忍,再正常不过的代码怎么会耗时这么久呢?如果在配置差一点的电脑上运行耗时会更久(惊呆了.jpg)。
我们不妨先阅读下面的内容,再来分析、解决上述耗时的问题。
基本概念
自从 jdk1.5 之后就有了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)。
自动装箱,就是 Java 自动将原始(基本)类型转换成对应的封装器(对象)类型的过程,比如将 int
的变量转换成 Integer
对象,这个过程叫做装箱。
自动拆箱,就是 Java 自动将封装器(对象)类型转换成基本类型的过程,如将 Integer
对象转换成 int
类型值,这个过程叫做拆箱。
之所以称之为自动装箱和拆箱,是因为这些操作并非人工(程序猿)操作的,而是 Java 自带的一个特性。
下表是 Java 中的基本类型和对应的封装类型的对应表:
基本类型|封装器类
—|—|—
int|Integer
byte|Byte
long|Long
float|float
double|Double
char|Character
boolean|Boolean
自动装箱示例:
1 | int a = 3; |
自动拆箱示例:
1 | Integer b = new Integer(7); |
Integer/int 自动拆箱和装箱
下面这段代码是 Integer
的源码中 valueOf
方法。
1 | /** |
我们在执行下面的这句代码,如下:
1 | Integer i = 100; |
上面的代码等同于下面的代码:
1 | Integer i = Integer.valueOf(100); |
结合上面的源码可以看出来,如果数值在 [-128,127]
之间(双闭区间),不会重新创建 Integer
对象,而是从缓存中(常量池)直接获取,从常量池中获取而不是堆栈操作,读取数据要快很多。
我们再来看一下常见的基础面试题(请给出打印结果),如下:
1 | public static void main(String[] args) { |
分析结果:
⓵: false, 两个对象进行比较分别指向了不同堆内存
⓶: true, 自动装箱且数值在 [-128,127] 之间(双闭区间)
⓷: false, 自动装箱且数值不在 [-128,127] 之间(双闭区间)
⓸: true, 自动拆箱且数值在 [-128,127] 之间(双闭区间)
解析耗时问题
类 Long
对应的也有一个 valueof
方法,源码如下:
1 | public static Long valueOf(long l) { |
这个和 Integer
的很像,道理上面说过,这里不再赘述。
在开篇的例子中,getCounterResult
方法有下面这句代码,如下:
1 | Long sum = 0L; |
很明显我们声明了一个 Long
的对象 sum
,由于自动装箱,这句代码并没有语法上面的错误,编译器当然也不会报错。上面代码等同于如下代码:
1 | Long sum = Long.valueof(0); |
在 for
循环中,超过 [-128,127]
就会创建新的对象,这样不断的创建对象,不停的申请堆内存,程序执行自然也就比较耗时了。
修改一下代码,如下:
1 | private static long getCounterResult() { |
执行时间大大缩短。
优柔寡断,是人生最大的负能量。对,别犹豫了赶紧扫码关注~