以下代码输出为多少?
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "a" + "b"; String s4 = s1 + s2; String s5 = "ab"; String s6 = s4.intern(); System.out.println("3,4 " + (s3 == s4)); System.out.println("3,5 " + (s3 == s5)); System.out.println("3,6 " + (s3 == s6)); System.out.println("4,5 " + (s4 == s5)); System.out.println("4,6 " + (s4 == s6)); System.out.println("5,6 " + (s5 == s6)); }
解析:
我们先来看s3,s4,s5这三个字符串,在看这道题之前我们先来一个个看每个字符串是怎么诞生的。
先看看最简单的s5
先了解一下常量池跟串池的关系
常量池:最初存在于字节码文件中,运行时,加载到运行时常量池,此时定义的常量,如a,b,ab,还只是一些符号,还没有变为java中的字符串对象
1. 当执行到 ldc #2 //String a 这一行的时候。会把a符号变为"a"字符串对象,并且开辟一个新的空间:StringTable[],这个就是串池,然后在串池中寻找有没有"b"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”]
2. 当执行到 ldc #3 //String b 这一行的时候。会把b符号变为"b"字符串对象,然后在串池中寻找有没有"a"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”,“b”]
3. 当执行到 ldc #4 //String ab 这一行的时候。会把ab符号变为"ab"字符串对象,然后在串池中寻找有没有"ab"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”,“b”,“ab”]
- s3:
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s5 = "ab"; String s3 = "a" + "b"; }
使用javap -v 反编译来查看
从反编译文件中我们能看出来,jvm在执行到9:的时候已经将s1,s2,s5的"a",“b”,“ab"传入到串池中,比较时都是引用串池中的"ab”,所以s3==s5为true。
- s4:
public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s4 = s1 + s2; }
由图我们可以看出来先调用了new StringBuilder()方法,补充(astore_1是将其存入局部变量表中,aload_1是将其拿出),在13:中拿到s1这个参数,在14:使用append方法将其传入,在17:加载了s2这个参数,在18:使用append方法将其传入,最后使用toString方法拼接字符串。
String s4 = s1 + s2;//这一行代码可以看成 String s4_new = new StringBuilder().append("a").append("b").toSring();
而我们通过以下toString方法的源代码,可以看出其是new了一个新的String;
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
一旦new了新的字符串来开辟新的空间所以其地址值不一样,一旦new对象之后,地址值都会变
当我们比较s3和s4的时候,s3的"ab"是在串池中的"ab",而s4引用的是一个新的字符串对象,虽然值一样,但是s3中的"ab"是在串池中的,而s4的"ab"是在堆中的对象,所以当我们打印s3==s4时,结果为false
也可以看下图,步骤更为清晰
- 在讲s6之前我们要先将StringTable(串池)的特性和intern方法
- 常量池中的字符串仅仅是符号,第一次用到时才变为对象字符串变量拼接的原理是StringBuilder(1.8)字符串常量拼接的原理是编译器优化可以使用intern方法,主从将串池中还没有的字符串对象放入串池
举个栗子:
String s1 = new String("a") + new String("b"); String s2 = s1.intern(); System.out.println(x2 == "ab");
通过上面的学习
当第一行行代码执行结束后,“a”,“b"被放入串池中
但是 new String(“a”)和new String(“b”)因为是new出来的,所以是在堆当中,与常量池中的不一样,还要一个new String(“ab”),但是由于它是动态拼接,所以还没有进入串池中
第二行的s2.intern();意思就是尝试将s2这个字符串放如串池中,如果有则不会放入,没有就放入,会把串池中的对象返回,此时"a”,“b”,"ab"均被放入串池中
此时的s2引用的对象就是串池中的"ab"
第三行中比较的均为串池中的对象所以结果为true
补充:以上intern方法是在jdk1.8为基础的前提下执行的,那么在jdk1.6有什么区别呢?
1.8:将这个字符串对象尝试放入串池,如果有则不放入,如果没有则会放入串池,会把串池中的对象返回
1.6:将这个字符串对象尝试放入串池,如果有则不放入,如果没有则会复制一份放入串池,会把串池中的对象返回
那么有什么区别呢?
我们在两个环境下执行以下相同代码
public static void main(String[] args) { String x = "ab"; String s = new String("a") + new String("b"); String s2 = s.intern(); System.out.println(s == x); System.out.println(s2 == x); }
我们先分析以下:
第一行执行结束,串池中有:“ab”
第二行执行结束,串池中有:“ab”,“a”,“b”,堆中有 new String(“a”) new String(“b”) new String(“ab”)
第三行执行结束,串池中有:“ab”,“a”,“b”,尝试将s放入串池,但是串池中已经有了"ab",所以s2引用的就是串池中的"ab"
所以:s是堆中的new String(“ab”),s2则是串池中的"ab"。
//StringTable:["a","b","ab"] public static void main(String[] args) { String x = "ab"; String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab") String s2 = s.intern();//s-->堆中的new String("ab"),s2-->StringTable:["a","b","ab"]中的ab System.out.println(s == x);//false System.out.println(s2 == x);//true }
当我们把String x = “ab”;放到第三行
这是1.8环境下运行的结果
//StringTable:["a","b","ab"] public static void main(String[] args) { String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab") String s2 = s.intern();//堆new String("a") new String("b") s-->new String("ab") s2-->StringTable:["a","b","ab"]的ab String x = "ab";//x-->StringTable:["a","b","ab"]的ab System.out.println(s == x);//true System.out.println(s2 == x);//true }
分析:
第一行执行完,我们在串池中加入了"a",“b”,堆中加入了new String(“a”) new String(“b”) new String(“ab”)
第二行执行完,尝试着吧"ab"放入串池,这个时候串池中没有"ab",所以成功放入了,这个时候堆中有new String(“a”) new String(“b”), s–>new String(“ab”) s2–>StringTable:[“a”,“b”,“ab”]的ab,(我用指针代表的是引用的对象)
第三行,因为串池中有"ab",所以直接引用串池中的"ab",x–>StringTable:[“a”,“b”,“ab”]的ab
这是1.6环境下运行的结果
//StringTable:["a","b","ab"] public static void main(String[] args) { String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab") String s2 = s.intern();//堆new String("a") new String("b") s-->new String("ab") s2-->StringTable:["a","b","ab"]的ab String x = "ab";//x-->StringTable:["a","b","ab"]的ab System.out.println(s == x);//false System.out.println(s2 == x);//true }
最后解答一开始的问题:
//StringTable["a","b","ab"] public static void main(String[] args) { String s1 = "a";//将"a"放入串池 String s2 = "b";//将"b"放入串池 String s3 = "a" + "b";//将"ab"放入串池,s3->StringTable["a","b","ab"]中的ab String s4 = s1 + s2;//堆中 s4->new String("ab") String s5 = "ab";//串池中有"ab",所以,s5->StringTable["a","b","ab"]中的ab String s6 = s4.intern();//因为StringTable["a","b","ab"]有"ab",所以s4->new String("ab") s6->StringTable["a","b","ab"]中的ab System.out.println("3,4 " + (s3 == s4));//false System.out.println("3,5 " + (s3 == s5));//true System.out.println("3,6 " + (s3 == s6));//true System.out.println("4,5 " + (s4 == s5));//false System.out.println("4,6 " + (s4 == s6));//false System.out.println("5,6 " + (s5 == s6));//true }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)