本文主要是介绍从源码里的一个注释,我追溯到了12年前,有点意思,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
那天我正在用键盘疯狂的输出:
突然微信弹出一个消息,是一个读者发给我的。
我点开一看:
啊,这熟悉的味道,一看就是 HashMap,八股文梦开始的地方啊。
但是他问出的问题,似乎又不是一个属于 HashMap 的八股文:
为什么这里要把 table 变量赋值给 tab 呢?
table 大家都知道,是 HashMap 的一个成员变量,往 map 里面放的数据就存储在这个 table 里面的:
在 putVal 方法里面,先把 table 赋值给了 tab 这个局部变量,后续在方法里面都是操作的这个局部变量了。
其实,不只是 putVal 方法,在 HashMap 的源码里面,“tab= table” 这样的写发多达 14 个,比如 getNode 里面也是这样的用法:
我们先思考一下,如果不用 tab 这个局部变量,直接操作 table,会不会有问题?
从代码逻辑和功能上来看,是不会有任何毛病的。
如果是其他人这样写,我会觉得可能是他的编程习惯,没啥深意,反正又不是不能用。
但是这玩意可是 Doug Lea 写的,隐约间觉得必然是有深意在里面的。
所以为什么要这样写呢?
巧了,我觉得我刚好知道答案是什么。
因为我在其他地方也看到过这种把成员变量赋值给局部变量的写法,而且在注释里面,备注了自己为什么这么写。
而这个地方,就是 Java 的 String 类:
比如 String 类的 trim 方法,在这个方法里面就把 String 的 value 赋给了 val 这个局部变量。
然后旁边给了一个非常简短的注释:
avoid getfield opcode
本文的故事,就从一行注释开始,一路追溯到 2010 年,我终于抽丝剥茧找到了问题的答案。
一行注释,就是说要避免使用 getfield 字节码。
虽然我不懂是啥意思,但是至少我拿到了几个关键词,算是找到了一个“线头”,接下来的事情就很简单了,顺着这个线头往下缕就完事了。
而且直觉上告诉我这又是一个属于字节码层面的极端的优化,缕到最后一定是一个骚操作。
那么我就先给你说结论了:这个代码确实是 Doug Lea 写的,在当年确实是一种优化手段,但是时代变了,放到现在,确实没有卵用。
答案藏在字节码
既然这里提到了字节码的操作,那么接下来的思路就是对比一下这两种不同写法分别的字节码是长啥样的不就清楚了吗?
比如我先来一段这样的测试代码:
public class MainTest {private final char[] CHARS = new char[5];public void test() {System.out.println(CHARS[0]);System.out.println(CHARS[1]);System.out.println(CHARS[2]);}public static void main(String[] args) {MainTest mainTest = new MainTest();mainTest.test();}
}
上面代码中的 test 方法,编译成字节码之后,是这样的:
可以看到,三次输出,对应着三次这样的字节码:
在网上随便找个 JVM 字节码指令表,就可以知道这几个字节码分别在干啥事儿:
- getstatic:获取指定类的静态域, 并将其压入栈顶
- aload_0:将第一个引用类型本地变量推送至栈顶
- getfield:获取指定类的实例域, 并将其值压入栈顶
- iconst_0:将int型0推送至栈顶
- caload:将char型数组指定索引的值推送至栈顶
- invokevirtual:调用实例方法
如果,我把测试程序按照前面提到的写法修改一下,并重新生成字节码文件,就是这样的:
可以
这篇关于从源码里的一个注释,我追溯到了12年前,有点意思的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!