关于String的一些问题小结

二.关于广泛流传的new String(“XYZ”);到底产生几个对象的问题的探讨

String   a   =   new   String("xyz");①
String   b  =   new   String("xyz");②

①Class被CLassLoader加载时,你的”xyz”被作为常量读入,在constant pool里创建了一个共享的”xyz”,然后当调用到new String(“xyz”)的时候,会在heap里创建这个
new String(“xyz”);(注意,在jdk1.7以及以上的版本中,常量池已经不再在方法区中而是在堆中)
②由于constant pool中存在”xyz”所以不再创建”xyz”,然后创建新的new String(“xyz”)。
对于String c = new String(“xyz”);的代码,与String a = “abc”不同的是String c = new String(“xyz”);在堆中创建新对象, 也就是说只要是显式的new一个String对象就会在堆中创建一个新的String对象,不管是否在常量池中以及有了该字符串。而用这种方式是如果常量池中没有对应的字符串,需要先在常量池中加入该字符串对象,然后再在堆中创建该字符串。这一过程中生成了两个对象,一个引用,该引用指向堆中的对象。
如果是常量池中已经存在的,只需要在堆中创建一个对象即可。
二,我们来看一下关于常量池的问题
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
我们来看一个网上常见的例子来讲解常量池。

package wangcc.stringpool;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = "Hel" + "lo";
        String s4 = "Hel" + new String("lo");
        String s5 = new String("Hello");
        String s6 = s5.intern();
        String s7 = "Hel";
        String s8 = "lo";
        String s9 = s7 + s8;
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // true
        System.out.println(s1 == s4); // false
        System.out.println(s1 == s9); // false
        System.out.println(s4 == s5); // false
        System.out.println(s1 == s6); // true
    }

}

关于s1==s2,很明显他们是指向同一个内存地址的。
s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。
关于s1 == s3,这个地方需要注意,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”;,所以s1 == s3成立。
关于s1==s4,这是不相等的。s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,会在堆中创建一个内容为
“lo”的String对象。而s4也会指向在堆中创建的一个对象,对象的内容为”Hello”。
s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。
s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。
必须要关注编译期的行为,才能更好的理解常量池。
运行时常量池中的常量,基本来源于各个class文件中的常量池。
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。
三.关于String的intern()方法
在上面我们已经带了一下String的intern()方法,那么我们来看一下intern()方法。
intern()方法的作用:就是重用String对象,以节省内存消耗。
关于intern()方法,具体介绍我觉得有一篇博文写的很好,就直接把它拿过来吧。http://blog.csdn.net/seu_calvin/article/details/52291082
我们要重点关注他在jdk6和jdk7及以上版本的作用的区别。
intern()方法在JDK1.6中的作用是:比如String s = new String(“SEU_Calvin”),再调用s.intern(),此时返回值还是字符串”SEU_Calvin”,表面上看起来好像这个方法没什么用处。但实际上,在JDK1.6中它做了个小动作:检查字符串池里是否存在”SEU_Calvin”这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把”SEU_Calvin”添加到字符串池中,然后再返回它的引用。
在JDK1.7中,常量池中不需要一定存储一份对象了,也可以直接存储堆中的引用。

你可能感兴趣的:(String)