最近在阅读《深入理解Jav虚拟机》的运行时常量池章节,看到“java语言并不要求常量池一定只有编译器才能产生...运行期间也可以将新的常量放入常量池,这种特性被开发人员利用得比较多的时String类的intern()方法。"于是我便去深入了解了一下。
1 public static void main(String[] args) {
2 String a="王者";
3 String b="荣耀";
4 String c=a+b;
5 }
上述代码中,我通过javap命令查看编译期的常量池,发现只有字符串"王者"和"荣耀",并无”王者荣耀“,通过反编译得到的String c=a+b变成了,原来+号会被转为StringBuilder,并且拼接,注意当String c="王者"+"荣耀"时,编译器则会优化,常量池里只有王者荣耀,并无“王者”和“”
1 String c= (new StringBuilder()).append(a).append(b).toString()
这就很有意思了,也就是说编译时常量池中并无"王者荣耀"了,因为编译期不会运算,只有在运行期才能执行方法得到结果。
由上图可知,"王者荣耀"直接指向堆,并且运行时常量池无"王者荣耀"
1 public static void main(String[] args) {
2 String a="王者";
3 String b="荣耀";
4 String c=a+b;
5 String d=c.intern();
6 System.out.println(d==c);
7 }
上面为什么是true呢,接下来讲述inter()
JDK1.7后,intern方法先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,当字符串常量池中找不到对应的字符串时,而只是生成一个对该字符串的引用在字符串常量池。所以”王者荣耀”本身不会在字符串常量池,而常量池里面保存了在堆中王者荣耀的索引,所以为true。JDK1.7之前这里就不说了。
前面说了intern可以在程序运行时将新的常量放入运行时常量池,接下来就开始演示intern的强大用处
public static void main(String[] args) throws Exception {
long start=System.currentTimeMillis(); //获取开始时间
String[] arr = new String[10000000];
Integer[] a= new Integer[10000000];
for (int i = 0; i < 10000000; i++) {
a[i] = (int)(1+Math.random()*(3));
}
for (int i = 0; i < 10000000; i++) {
arr[i] = new String(String.valueOf(a[i]));
}
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
}
//程序运行时间: 14461ms
public static void main(String[] args) throws Exception {
long start=System.currentTimeMillis(); //获取开始时间
String[] arr = new String[10000000];
Integer[] a= new Integer[10000000];
for (int i = 0; i < 10000000; i++) {
a[i] = (int)(1+Math.random()*(3));
}
for (int i = 0; i < 10000000; i++) {
arr[i] = new String(String.valueOf(a[i])).intern();
}
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
}
//程序运行时间: 1688ms
很简单的小程序,就是有无调用intern方法,但程序运行时间差了10倍!
我们明确的知道,会有很多重复的相同的字符串产生,但是这些字符串的值都是只有在运行期才能确定的。所以,我们通过intern显示的将其加入常量池,这样可以减少很多字符串的重复创建。
new String("王者荣耀")和new String("王者")+new String("荣耀")不管是在编译期就确定放入常量池还是在运行期,都还是要在堆里面创建对象的。不同就是new String("王者荣耀")还要指向常量池。new String("王者")+new String("荣耀")不需要指向常量池
new String.intern中。这个过程是不会在Java堆中再创建一个String对象的。
注意,是因为随机数是运行期才知道的,intern把最先出现的,”1“,“2”,”3“,对象索引放入了常量池,下次在看见”1“,“2”,”3“,返回第一次在堆中String的对象地址,不用创建新对象,所以intern高效,使用intern()在堆中只会创建3个对象,而不使用intern则创建100000000个对象,并且常量池里并没有”1“,“2”,”3“
小问题,arri[]=new String("王者荣耀").intern()没什么意义的,虽然这样写比arri[]=new String("王者荣耀")效率高,可用代码验证,arri[]=new String("王者荣耀").intern()编译时常量池就出现“王者荣耀”,因为intern不会产生对象所以快,这里是直接引用了常量池中的字符串“王者荣耀”。但intern()并不是这样的初衷,而是为了解决运行时才出现的常量,不是解决编译时在字符串常量池的问题,这里比较抽象,多理解一下
笔者技术有限,如有错误,请指正,谢谢